A Terraform provider for managing Portkey resources through the Portkey Admin API.
This provider enables you to manage:
- Workspaces: Create, update, and manage workspaces for organizing teams and projects
- Workspace Members: Assign users to workspaces with specific roles
- User Invitations: Send invitations to users with organization and workspace access
- Users: Query existing users in your organization (read-only)
- Integrations: Manage AI provider connections (OpenAI, Anthropic, Azure, etc.)
- Integration Workspace Access: Enable integrations for specific workspaces with optional usage/rate limits
- Providers (Virtual Keys): Create workspace-scoped API keys for AI providers
- Configs: Define gateway configurations with routing, fallbacks, and load balancing
- Prompts: Manage versioned prompt templates
- Guardrails: Set up content moderation, validation rules, and safety checks
- Usage Limits Policies: Control costs with spending limits and thresholds
- Rate Limits Policies: Manage request rates per minute/hour/day
- API Keys: Create and manage Portkey API keys (organization and workspace-scoped)
terraform {
required_providers {
portkey = {
source = "portkey-ai/portkey"
version = "~> 0.1"
}
}
}git clone https://github.com/Portkey-AI/terraform-provider-portkey
cd terraform-provider-portkey
make installThis will build and install the provider in your local Terraform plugins directory.
The provider requires a Portkey Admin API key. You can provide this in one of two ways:
export PORTKEY_API_KEY="your-admin-api-key"provider "portkey" {
api_key = "your-admin-api-key"
}- Log in to your Portkey dashboard
- Navigate to Admin Settings
- Create an Organization Admin API key
- Ensure you have Organization Owner or Admin role
Note: Admin API keys provide broad access to your organization. Store them securely and never commit them to version control.
terraform {
required_providers {
portkey = {
source = "portkey-ai/portkey"
}
}
}
provider "portkey" {
# API key read from PORTKEY_API_KEY environment variable
}# Create a workspace
resource "portkey_workspace" "production" {
name = "Production"
description = "Production environment"
}
# Create an integration for OpenAI
resource "portkey_integration" "openai" {
name = "OpenAI Production"
ai_provider_id = "openai"
key = var.openai_api_key
}
# Create a provider (virtual key) linked to the integration
resource "portkey_provider" "openai_prod" {
name = "OpenAI Production Key"
workspace_id = portkey_workspace.production.id
integration_id = portkey_integration.openai.id
}
# Create a gateway config with retry logic
resource "portkey_config" "production" {
name = "Production Config"
workspace_id = portkey_workspace.production.id
config = jsonencode({
retry = {
attempts = 3
on_status_codes = [429, 500, 502, 503]
}
cache = {
mode = "simple"
}
})
}
# Create a prompt template
resource "portkey_prompt" "assistant" {
name = "AI Assistant"
collection_id = "your-collection-id"
virtual_key = portkey_provider.openai_prod.id
model = "gpt-4"
string = "You are a helpful assistant. User: {{user_input}}"
parameters = jsonencode({
temperature = 0.7
max_tokens = 1000
})
}
# Create a guardrail for content moderation
resource "portkey_guardrail" "content_filter" {
name = "Content Filter"
workspace_id = portkey_workspace.production.id
# checks and actions must be JSON-encoded
checks = jsonencode([
{
id = "default.wordCount"
parameters = {
minWords = 1
maxWords = 5000
}
}
])
actions = jsonencode({
onFail = "block"
message = "Content validation failed"
})
}
# Create a usage limits policy
resource "portkey_usage_limits_policy" "cost_limit" {
name = "Monthly Cost Limit"
workspace_id = portkey_workspace.production.id
type = "cost"
credit_limit = 1000.0
alert_threshold = 800.0
periodic_reset = "monthly"
# conditions and group_by must be JSON-encoded arrays
conditions = jsonencode([
{ key = "workspace_id", value = portkey_workspace.production.id }
])
group_by = jsonencode([
{ key = "api_key" }
])
}
# Create a rate limits policy
resource "portkey_rate_limits_policy" "api_rate" {
name = "API Rate Limit"
workspace_id = portkey_workspace.production.id
type = "requests"
unit = "rpm"
value = 100
# conditions and group_by must be JSON-encoded arrays
conditions = jsonencode([
{ key = "workspace_id", value = portkey_workspace.production.id }
])
group_by = jsonencode([
{ key = "api_key" }
])
}
# Create a Portkey API key for your application
resource "portkey_api_key" "backend" {
name = "Backend Service Key"
type = "workspace"
sub_type = "service"
workspace_id = portkey_workspace.production.id
scopes = [
"logs.list",
"logs.view",
"configs.read",
"providers.list"
]
}# Invite a user with workspace access
resource "portkey_user_invite" "data_scientist" {
email = "scientist@example.com"
role = "member"
workspaces = [
{
id = portkey_workspace.production.id
role = "admin"
}
]
scopes = [
"logs.export",
"logs.list",
"logs.view",
"configs.read",
"configs.list",
"virtual_keys.read",
"virtual_keys.list"
]
}
# Add an existing user to a workspace
resource "portkey_workspace_member" "senior_engineer" {
workspace_id = portkey_workspace.production.id
user_id = "existing-user-id"
role = "manager"
}
# Query all workspaces
data "portkey_workspaces" "all" {}
# Query all users
data "portkey_users" "all" {}For self-hosted Portkey deployments, configure the base URL:
provider "portkey" {
api_key = var.portkey_api_key
base_url = "https://your-portkey-instance.com/v1"
}Manages a Portkey workspace.
| Argument | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Name of the workspace |
description |
String | No | Description of the workspace |
Import: terraform import portkey_workspace.example workspace-id
Manages workspace membership for users.
| Argument | Type | Required | Description |
|---|---|---|---|
workspace_id |
String | Yes | ID of the workspace |
user_id |
String | Yes | ID of the user |
role |
String | Yes | Role: admin, manager, member |
Import: terraform import portkey_workspace_member.example workspace-id/member-id
Sends invitations to users.
| Argument | Type | Required | Description |
|---|---|---|---|
email |
String | Yes | Email address to invite |
role |
String | Yes | Organization role: admin, member |
workspaces |
List | No | Workspaces to add user to |
scopes |
List | No | API scopes for the user |
Note: User invitations cannot be updated. To change an invitation, delete and recreate it.
Manages AI provider integrations (organization-level).
| Argument | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Name of the integration |
ai_provider_id |
String | Yes | Provider: openai, anthropic, azure-openai, aws-bedrock, etc. |
key |
String | No | API key for the provider (write-only) |
configurations |
String (JSON) | No | Provider-specific configurations (write-only) |
description |
String | No | Description |
Import: terraform import portkey_integration.example integration-slug
Note: The key and configurations fields are write-only and cannot be retrieved from the API after creation. When importing, you must manually add these values to your configuration.
resource "portkey_integration" "bedrock" {
name = "AWS Bedrock Production"
ai_provider_id = "aws-bedrock"
configurations = jsonencode({
aws_role_arn = "arn:aws:iam::123456789012:role/PortkeyBedrockRole"
aws_region = "us-east-1"
aws_external_id = "your-external-id" # Optional
})
}resource "portkey_integration" "bedrock_keys" {
name = "AWS Bedrock (Access Keys)"
ai_provider_id = "aws-bedrock"
key = var.aws_secret_access_key
configurations = jsonencode({
aws_access_key_id = var.aws_access_key_id
aws_region = "us-east-1"
})
}Azure OpenAI requires a specific configuration format with authentication mode and deployment details:
resource "portkey_integration" "azure_openai" {
name = "Azure OpenAI"
ai_provider_id = "azure-openai"
key = var.azure_api_key
configurations = jsonencode({
azure_auth_mode = "default" # Required: "default", "entra", or "managed"
azure_resource_name = "my-azure-resource"
azure_deployment_config = [
{
azure_deployment_name = "gpt-4-deployment"
azure_api_version = "2024-02-15-preview"
azure_model_slug = "gpt-4" # Model type: gpt-4, gpt-35-turbo, etc.
is_default = true
}
]
})
}Azure OpenAI Configuration Fields:
| Field | Type | Required | Description |
|---|---|---|---|
azure_auth_mode |
String | Yes | Authentication mode: "default", "entra", or "managed" |
azure_resource_name |
String | Yes | Your Azure OpenAI resource name |
azure_deployment_config |
Array | Yes | Array of deployment configurations (min 1) |
azure_deployment_config[].azure_deployment_name |
String | Yes | Deployment name from Azure OpenAI Studio |
azure_deployment_config[].azure_api_version |
String | Yes | API version (e.g., "2024-02-15-preview"), max 30 chars |
azure_deployment_config[].azure_model_slug |
String | Yes | Model identifier (e.g., "gpt-4", "gpt-35-turbo") |
azure_deployment_config[].is_default |
Boolean | No | Set to true for the default deployment |
azure_deployment_config[].alias |
String | No* | Alias for the deployment (*required if not default) |
azure_entra_tenant_id |
String | For entra | Azure AD tenant ID (required for entra auth) |
azure_entra_client_id |
String | For entra | Azure AD client ID (required for entra auth) |
azure_entra_client_secret |
String | For entra | Azure AD client secret (required for entra auth) |
azure_managed_client_id |
String | For managed | Managed identity client ID (optional for managed auth) |
Authentication Modes:
default- Uses the API key provided in thekeyfieldentra- Uses Microsoft Entra ID for authenticationmanaged- Uses Azure Managed Identity for authentication
Multiple Deployments Example (API Key Auth):
resource "portkey_workspace" "production" {
name = "Production"
description = "Production environment workspace"
}
resource "portkey_integration" "azure_openai_multi" {
name = "Azure OpenAI Multi-Model"
ai_provider_id = "azure-openai"
key = var.azure_api_key
configurations = jsonencode({
azure_auth_mode = "default"
azure_resource_name = "my-azure-resource"
azure_deployment_config = [
{
azure_deployment_name = "gpt-4-deployment"
azure_api_version = "2024-02-15-preview"
azure_model_slug = "gpt-4"
is_default = true
},
{
alias = "gpt35"
azure_deployment_name = "gpt-35-turbo-deployment"
azure_api_version = "2024-02-15-preview"
azure_model_slug = "gpt-35-turbo"
}
]
})
}Microsoft Entra ID Authentication Example:
resource "portkey_integration" "azure_openai_entra" {
name = "Azure OpenAI (Entra ID)"
ai_provider_id = "azure-openai"
configurations = jsonencode({
azure_auth_mode = "entra"
azure_resource_name = "my-azure-resource"
azure_entra_tenant_id = var.azure_tenant_id
azure_entra_client_id = var.azure_client_id
azure_entra_client_secret = var.azure_client_secret
azure_deployment_config = [
{
azure_deployment_name = "gpt-4-deployment"
azure_api_version = "2024-02-15-preview"
azure_model_slug = "gpt-4"
is_default = true
}
]
})
}Managed Identity Authentication Example:
resource "portkey_integration" "azure_openai_managed" {
name = "Azure OpenAI (Managed Identity)"
ai_provider_id = "azure-openai"
configurations = jsonencode({
azure_auth_mode = "managed"
azure_resource_name = "my-azure-resource"
azure_managed_client_id = var.azure_managed_client_id # Optional
azure_deployment_config = [
{
azure_deployment_name = "gpt-4-deployment"
azure_api_version = "2024-02-15-preview"
azure_model_slug = "gpt-4"
is_default = true
}
]
})
}Manages workspace access for integrations. Enables an integration to be used within a specific workspace.
| Argument | Type | Required | Description |
|---|---|---|---|
integration_id |
String | Yes | Integration slug or ID |
workspace_id |
String | Yes | Workspace ID to grant access to |
enabled |
Boolean | No | Whether access is enabled (default: true) |
usage_limits |
Block | No | Usage limits for this workspace |
rate_limits |
Block | No | Rate limits for this workspace |
Import: terraform import portkey_integration_workspace_access.example integration-slug/workspace-id
resource "portkey_integration_workspace_access" "openai_dev" {
integration_id = portkey_integration.openai.slug
workspace_id = portkey_workspace.dev.id
}resource "portkey_integration_workspace_access" "openai_dev" {
integration_id = portkey_integration.openai.slug
workspace_id = portkey_workspace.dev.id
enabled = true
usage_limits = [{
type = "cost"
credit_limit = 100
alert_threshold = 80
periodic_reset = "monthly"
}]
rate_limits = [{
type = "requests"
unit = "rpm"
value = 1000
}]
}Manages providers (virtual keys) - workspace-scoped API keys for AI providers.
| Argument | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Name of the provider |
workspace_id |
String | Yes | Workspace ID (UUID) |
integration_id |
String | Yes | Integration ID to link to |
note |
String | No | Notes |
Import: terraform import portkey_provider.example workspace-id:provider-id
Manages gateway configurations with routing, fallbacks, and caching.
| Argument | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Name of the config |
workspace_id |
String | Yes | Workspace ID |
config |
String (JSON) | Yes | Configuration object |
is_default |
Number | No | Whether this is the default config |
Import: terraform import portkey_config.example config-slug
Manages versioned prompt templates.
| Argument | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Name of the prompt |
collection_id |
String | Yes | Collection ID |
string |
String | Yes | Prompt template |
virtual_key |
String | Yes | Provider ID to use |
model |
String | Yes | Model name |
parameters |
String (JSON) | No | Model parameters |
Import: terraform import portkey_prompt.example prompt-slug
Manages content validation and safety checks.
| Argument | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Name of the guardrail |
workspace_id |
String | No | Workspace ID (or use organisation_id) |
organisation_id |
String | No | Organisation ID |
checks |
List | Yes | Validation checks to perform |
actions |
Object | Yes | Actions on check failure |
Import: terraform import portkey_guardrail.example guardrail-slug
Manages spending limits and cost controls.
| Argument | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Name of the policy |
workspace_id |
String | Yes | Workspace ID |
type |
String | Yes | cost or tokens |
credit_limit |
Number | Yes | Maximum usage allowed |
alert_threshold |
Number | No | Threshold for alerts |
periodic_reset |
String | No | monthly or weekly |
conditions |
List | Yes | Conditions to match |
group_by |
List | Yes | Fields to group usage by |
Import: terraform import portkey_usage_limits_policy.example policy-id
Manages request rate limiting.
| Argument | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Name of the policy |
workspace_id |
String | Yes | Workspace ID |
type |
String | Yes | requests or tokens |
unit |
String | Yes | rpm, rph, or rpd |
value |
Number | Yes | Rate limit value |
conditions |
List | Yes | Conditions to match |
group_by |
List | Yes | Fields to apply limits by |
Import: terraform import portkey_rate_limits_policy.example policy-id
Manages Portkey API keys (organization-scoped or workspace-scoped).
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Human-readable name |
type |
String | Yes | organisation or workspace |
sub_type |
String | Yes | service or user |
workspace_id |
String | No | Required for type = "workspace" keys |
user_id |
String | No | Required for sub_type = "user" keys |
description |
String | No | Optional description |
scopes |
List(String) | No | Permission scopes |
metadata |
Map(String) | No | Custom string metadata attached to every request |
alert_emails |
List(String) | No | Emails to notify on usage alerts |
config_id |
String | No | ID of a Portkey config to bind as the default for all requests |
allow_config_override |
Bool | No | Allow callers to override config_id at request time (default true) |
expires_at |
String | No | RFC3339 datetime when this key expires; can be updated after creation |
reset_usage |
Bool | No | Set true to trigger an immediate usage counter reset (write-only trigger) |
usage_limits |
Object | No | See usage_limits below |
rate_limits |
List(Object) | No | See rate_limits below |
rotation_policy |
Object | No | See rotation_policy below |
usage_limits nested object
| Argument | Type | Required | Description |
|---|---|---|---|
credit_limit |
Number | Yes (if block present) | Maximum credits (tokens or cost units) |
type |
String | No | tokens or cost — what the limit counts |
alert_threshold |
Number | No | Trigger alert emails at this usage level |
periodic_reset |
String | No | Reset cadence: monthly or weekly |
periodic_reset_days |
Number | No | Custom reset interval in days (1–365); alternative to periodic_reset |
next_usage_reset_at |
String | No/Computed | ISO8601 datetime for the next scheduled reset |
rate_limits nested object (list)
| Argument | Type | Required | Description |
|---|---|---|---|
type |
String | Yes | requests or tokens |
unit |
String | Yes | rpm, rph, rpd, rps, rpw |
value |
Number | Yes | Limit value |
rotation_policy nested object
| Argument | Type | Required | Description |
|---|---|---|---|
rotation_period |
String | No | weekly or monthly |
next_rotation_at |
String | No/Computed | ISO8601 datetime for the next scheduled rotation; computed by the API |
key_transition_period_ms |
Number | No | Overlap window in ms after rotation during which both old and new keys are valid (minimum 1800000 — 30 min) |
Read-only attributes: id, key (sensitive), organisation_id, status, created_at, updated_at, last_reset_at
Import: terraform import portkey_api_key.example api-key-id
| Data Source | Description | Key Arguments |
|---|---|---|
portkey_workspace |
Fetch a single workspace | id |
portkey_workspaces |
List all workspaces | - |
portkey_user |
Fetch a single user | id |
portkey_users |
List all users | - |
| Data Source | Description | Key Arguments |
|---|---|---|
portkey_integration |
Fetch a single integration | slug |
portkey_integrations |
List all integrations | - |
portkey_integration_workspaces |
List workspace access for an integration | integration_id |
portkey_provider |
Fetch a single provider | id, workspace_id |
portkey_providers |
List providers in workspace | workspace_id |
portkey_config |
Fetch a single config | slug |
portkey_configs |
List configs | workspace_id (optional) |
portkey_prompt |
Fetch a single prompt | id_or_slug |
portkey_prompts |
List prompts | workspace_id, collection_id (optional) |
| Data Source | Description | Key Arguments |
|---|---|---|
portkey_guardrail |
Fetch a single guardrail | id_or_slug |
portkey_guardrails |
List guardrails | workspace_id or organisation_id |
portkey_usage_limits_policy |
Fetch a usage limits policy | id |
portkey_usage_limits_policies |
List usage limits policies | workspace_id |
portkey_rate_limits_policy |
Fetch a rate limits policy | id |
portkey_rate_limits_policies |
List rate limits policies | workspace_id |
| Data Source | Description | Key Arguments |
|---|---|---|
portkey_api_key |
Fetch a single API key | id |
portkey_api_keys |
List API keys | workspace_id (optional) |
make build# Run unit tests
go test ./...
# Run acceptance tests (requires valid API key)
make testaccmake installmake generateWhen creating API keys or inviting users, you can grant scopes from these categories:
logs.list,logs.view,logs.exportanalytics.view
configs.create,configs.read,configs.update,configs.delete,configs.list
providers.create,providers.read,providers.update,providers.delete,providers.listvirtual_keys.create,virtual_keys.read,virtual_keys.update,virtual_keys.delete,virtual_keys.list,virtual_keys.copy
prompts.create,prompts.read,prompts.update,prompts.delete,prompts.list,prompts.publish
guardrails.create,guardrails.read,guardrails.update,guardrails.delete,guardrails.list
policies.create,policies.read,policies.update,policies.delete,policies.list
workspaces.create,workspaces.read,workspaces.update,workspaces.delete,workspaces.listworkspace_users.create,workspace_users.read,workspace_users.update,workspace_users.delete,workspace_users.listorganisation_users.create,organisation_users.read,organisation_users.update,organisation_users.delete,organisation_users.list
organisation_service_api_keys.create,organisation_service_api_keys.read,organisation_service_api_keys.update,organisation_service_api_keys.delete,organisation_service_api_keys.listworkspace_service_api_keys.create,workspace_service_api_keys.read,workspace_service_api_keys.update,workspace_service_api_keys.delete,workspace_service_api_keys.listworkspace_user_api_keys.create,workspace_user_api_keys.read,workspace_user_api_keys.update,workspace_user_api_keys.delete,workspace_user_api_keys.list
organisation_integrations.create,organisation_integrations.read,organisation_integrations.update,organisation_integrations.delete,organisation_integrations.listworkspace_integrations.create,workspace_integrations.read,workspace_integrations.update,workspace_integrations.delete,workspace_integrations.list
admin- Full organization accessmember- Standard user access
admin- Full workspace accessmanager- Manage workspace resources and membersmember- Standard workspace access
Some resources have specific prerequisites that must be met before they can be created.
To create a provider, you need:
- A workspace ID (UUID format, not slug)
- An integration that is enabled for that workspace
# First, create or reference an integration
resource "portkey_integration" "openai" {
name = "OpenAI"
ai_provider_id = "openai"
key = var.openai_api_key
}
# Create a workspace
resource "portkey_workspace" "production" {
name = "Production"
}
# Enable the integration for the workspace
resource "portkey_integration_workspace_access" "openai_prod" {
integration_id = portkey_integration.openai.slug
workspace_id = portkey_workspace.production.id
}
# Then create a provider linked to the integration
resource "portkey_provider" "main" {
name = "Production OpenAI"
workspace_id = portkey_workspace.production.id
integration_id = portkey_integration.openai.slug
depends_on = [portkey_integration_workspace_access.openai_prod]
}Common error: 403 Forbidden - The integration is not enabled for the specified workspace. Use portkey_integration_workspace_access to enable it.
API keys require at least one scope:
resource "portkey_api_key" "backend" {
name = "Backend Service"
type = "organisation" # or "workspace"
sub_type = "service" # or "user"
scopes = ["providers.list", "logs.view"] # Required - at least one scope
}Common error: 502 Bad Gateway - No scopes provided. Always include at least one scope.
resource "portkey_api_key" "controlled" {
name = "Budget-Controlled Key"
type = "workspace"
sub_type = "service"
workspace_id = portkey_workspace.production.id
scopes = ["completions.write", "providers.list"]
usage_limits = {
type = "tokens" # count tokens (or "cost" for dollar spend)
credit_limit = 1000000 # 1M tokens
alert_threshold = 800000 # email alert at 800K
periodic_reset = "monthly" # auto-reset every month
}
rate_limits = [
{
type = "requests"
unit = "rpm" # requests per minute (also: rph, rpd, rps, rpw)
value = 500
}
]
alert_emails = ["platform-team@example.com"]
}To clear limits, remove the usage_limits or rate_limits block and re-apply. The provider sends a null to the API which clears the limits.
A rotation policy tells Portkey to automatically rotate this key on a schedule. After rotation, both the old and new key remain valid for key_transition_period_ms milliseconds so in-flight requests are not disrupted.
resource "portkey_api_key" "rotating" {
name = "Auto-Rotating Service Key"
type = "workspace"
sub_type = "service"
workspace_id = portkey_workspace.production.id
scopes = ["completions.write", "providers.list"]
rotation_policy = {
rotation_period = "monthly"
key_transition_period_ms = 3600000 # 1 hour overlap (minimum is 1800000 = 30 min)
}
}You can also set a specific next_rotation_at datetime to control exactly when the first rotation fires:
rotation_policy = {
rotation_period = "weekly"
next_rotation_at = "2026-05-01T00:00:00Z"
key_transition_period_ms = 1800000 # 30 min minimum
}next_rotation_at is computed by the API after the first rotation, so Terraform will read it back automatically on subsequent plans.
resource "portkey_api_key" "short_lived" {
name = "Short-Lived Key"
type = "workspace"
sub_type = "service"
workspace_id = portkey_workspace.production.id
scopes = ["completions.write"]
# Hard expiry — key is invalidated after this date regardless of rotation
expires_at = "2026-12-31T23:59:59Z"
rotation_policy = {
rotation_period = "monthly"
key_transition_period_ms = 3600000
}
}expires_at can be updated after creation — change the value and re-apply without destroying the key. To create a non-expiring key, simply omit the field.
All mutable fields can be updated in-place with terraform apply — no destroy/recreate is required (except for type, sub_type, workspace_id, and user_id which force replacement):
resource "portkey_api_key" "backend" {
name = "Backend Service — updated name" # update name
type = "workspace"
sub_type = "service"
workspace_id = portkey_workspace.production.id
scopes = ["completions.write", "providers.list", "logs.view"] # add scope
# Bind to a config after the fact
config_id = portkey_config.routing.id
allow_config_override = false # lock callers to this config
# Extend the expiry
expires_at = "2027-06-30T23:59:59Z"
usage_limits = {
credit_limit = 2000000 # increase limit
periodic_reset = "monthly"
}
rotation_policy = {
rotation_period = "monthly"
key_transition_period_ms = 3600000
}
}To immediately reset a key's usage counters without waiting for the next scheduled reset, set reset_usage = true and apply:
resource "portkey_api_key" "backend" {
name = "Backend Service"
type = "workspace"
sub_type = "service"
workspace_id = portkey_workspace.production.id
scopes = ["completions.write"]
usage_limits = {
credit_limit = 1000000
periodic_reset = "monthly"
}
reset_usage = true # triggers immediate counter reset on next apply
}reset_usage is a write-only trigger — it is never stored in Terraform state. After the reset is performed, state records null for this field. If you leave reset_usage = true in your config, the next terraform plan will show it as a pending change and every subsequent apply will reset the counters again. Remove the line (or set reset_usage = false) once the reset is done.
After a reset, last_reset_at (a read-only attribute) is updated by the API and will be visible in your state.
These resources require JSON-encoded fields:
# Use jsonencode() for complex fields
resource "portkey_guardrail" "example" {
name = "Content Filter"
workspace_id = var.workspace_id
checks = jsonencode([
{
id = "default.wordCount"
parameters = { minWords = 1, maxWords = 5000 }
}
])
actions = jsonencode({
onFail = "block"
message = "Validation failed"
})
}Common error: 400 Bad Request - Using HCL syntax instead of jsonencode().
| Resource | Cause | Solution |
|---|---|---|
portkey_provider |
Integration not enabled for workspace | Use portkey_integration_workspace_access resource to enable |
portkey_api_key |
Admin key lacks required scopes | Use an admin key with full permissions |
| Any resource | Workspace access not granted | Ensure your admin key has workspace access |
| Resource | Cause | Solution |
|---|---|---|
portkey_api_key |
Self-hosted API route differences | Verify base_url is correct for your deployment |
| Any resource | Resource was deleted externally | Run terraform refresh to sync state |
| Resource | Cause | Solution |
|---|---|---|
| Policies | Empty conditions array |
Provide at least one condition: jsonencode([{key="workspace_id", value="..."}]) |
| Guardrails | Invalid check format | Use jsonencode() for checks and actions |
| Configs | Invalid config JSON | Ensure config field contains valid JSON |
If Terraform shows unexpected diffs on every plan:
- Config resource: The API may normalize JSON differently. Use consistent formatting.
- Prompt resource: Template updates create new versions. Use name-only updates.
For self-hosted deployments, ensure base_url points to your instance:
provider "portkey" {
api_key = var.portkey_api_key
base_url = "https://your-portkey-instance.com/v1"
}Workspaces may fail to delete with error: 409: Unable to delete. Please ensure that all Virtual Keys are deleted. This occurs even for newly created workspaces due to auto-provisioned resources on the backend.
Workaround: Manually delete all providers/virtual keys in the workspace before destroying.
Workspaces with emoji characters in the name may fail to delete with error: Invalid value for the name parameter. The API's DELETE endpoint appears to have stricter validation than create/update endpoints.
Workaround: Rename the workspace to remove emoji characters before deleting, or delete via the Portkey UI.
Note: If deletion fails with "Invalid value" for name, first try running terraform refresh to sync state, then retry the destroy.
The workspace member getMember endpoint returns inconsistent data, which can cause Terraform state drift.
Status: Tests are skipped; resource works for create/update/delete operations.
The user update API rejects requests when updating to the same role value.
Impact: User resource is read-only (data source only). Use portkey_user_invite to manage user access.
User invitations cannot be updated - there is no PUT endpoint.
Workaround: Delete and recreate the invitation to modify it.
Updating a prompt's template or parameters creates a new version rather than updating in place. The new version is not automatically set as the default.
Workaround: Name updates work reliably. For template changes, use the Portkey UI or API directly to manage versions.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Submit a pull request
This provider is distributed under the Mozilla Public License 2.0. See LICENSE for more information.
- Documentation: Portkey Docs
- Admin API Reference: Admin API Docs
- Issues: GitHub Issues
- Community: Discord
- Portkey Gateway - Open-source AI Gateway
- Portkey Python SDK
- Portkey Node SDK