Managing Integrations via Terraform

The Lumos Terraform provider lets you manage your app integrations as code. You declare each integration as a lumos_app resource, then version, review, and apply it like any other infrastructure — instead of clicking through the Lumos UI. This guide is the Terraform counterpart to Managing Integrations via API.

Overview

A lumos_app resource represents a connection between Lumos and one of your apps (for example Okta or Google Workspace). With it you can declare an integration and its credentials in HCL, create it with terraform apply, read its state, rotate credentials by re-applying, import an integration that already exists, and remove it with terraform destroy. Managing integrations this way gives you repeatable, reviewable, and auditable change control.

Before you start

You need:

  • Terraform 1.0 or later.
  • The teamlumos/lumos provider (configured in the next section).
  • A Lumos API token. Create one in the Lumos UI under Settings → API Tokens. It's shown once — copy it somewhere safe. The provider reads it from the LUMOS_ACCESS_TOKEN environment variable.
  • Permission to manage integrations. The token acts as the user who created it, so that user must be an admin with permission to connect and remove integrations. Otherwise applies fail with HTTP 403.

Some apps (those that use OAuth or the Merge widget) can't be fully connected from Terraform alone — apply starts the connection and you finish authorizing it in the Lumos UI. See step 6.

Concept-to-operation map

I want to...How
Find what an integration needs to connectThe REST discovery endpoints — see step 1.
Connect a new integrationDeclare a lumos_app resource, then terraform apply.
Set credentials and configurationauth and settings arguments (as jsonencode({...})).
Read an integration's stateThe resource's computed attributes, e.g. disconnected and instance_id (via an output or terraform state show).
Rotate credentials / change fieldsEdit auth/settings and terraform apply.
Adopt an integration that already existsterraform import.
Disconnect an integrationRemove the resource and terraform apply, or terraform destroy.

Configure the provider

Declare the provider and authenticate. The token comes from the LUMOS_ACCESS_TOKEN environment variable, so you don't commit it to your configuration:

terraform {
  required_providers {
    lumos = {
      source  = "teamlumos/lumos"
      version = "~> 0.11"
    }
  }
}

provider "lumos" {
  # Authenticates with the LUMOS_ACCESS_TOKEN environment variable.
  # server_url defaults to https://api.lumos.com.
}
export LUMOS_ACCESS_TOKEN="your-lumos-api-token"

To set the token in configuration instead of the environment, use the sensitive http_bearer argument: provider "lumos" { http_bearer = var.lumos_token }.

Core workflow

1. Discover the integration and its connection schema

Before writing the resource, find the integration's app_class_id and check its connection schema — the definitive list of which credential keys go in auth and which configuration keys go in settings. Both are read from the REST API with the same token the provider uses:

# Find the app_class_id
curl -s "https://api.lumos.com/v1/integrations?query=okta" \
  -H "Authorization: Bearer $LUMOS_ACCESS_TOKEN"

# See what it needs to connect
curl -s "https://api.lumos.com/v1/integrations/okta.com/connection-schema" \
  -H "Authorization: Bearer $LUMOS_ACCESS_TOKEN"

For Okta, the schema shows an api_key credential in auth and the tenant domain as app_instance_identifier in settings. Other integrations use different keys — some name the tenant differently, some derive it from the credentials and need no setting at all. See the discovery section of Managing Integrations via API for how to read the schema in detail.

2. Declare an integration resource

Precondition: the provider is configured (previous section).

Give the resource the app's identifier in app_class_id:

resource "lumos_app" "okta" {
  app_class_id = "okta.com"
}

3. Set its fields

Put all credentials inside auth, encoded with jsonencode({...}), and non-secret configuration inside settings — shaped exactly as the connection schema from step 1 describes. Keep secrets in sensitive variables, not in the resource literally:

variable "okta_api_token" {
  type      = string
  sensitive = true
}

resource "lumos_app" "okta" {
  app_class_id = "okta.com"

  auth = jsonencode({
    api_key = var.okta_api_token
  })

  settings = jsonencode({
    app_instance_identifier = "your-org.okta.com"
  })
}

See the argument reference for every field.

4. Initialize, plan, and apply

Precondition: LUMOS_ACCESS_TOKEN is exported.

terraform init    # downloads the provider
terraform plan    # previews the change
terraform apply   # creates the integration

When the credentials are complete, apply connects the integration and Lumos starts the first sync automatically.

5. Trigger a sync

Lumos starts a sync automatically whenever an apply connects or reconnects an integration, so your first apply (step 4) already triggered one.

The provider manages connect/reconnect/disconnect — it does not manage syncs. To run a sync on demand afterwards (for example from a CI step after apply), call the REST sync endpoint directly with the resource's id. Read the id from state with terraform state show lumos_app.okta, or expose it with an output, then POST to /v1/apps/{id}/sync:

curl -s -X POST https://api.lumos.com/v1/apps/$INTEGRATION_ID/sync \
  -H "Authorization: Bearer $LUMOS_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "sync_type": "full_sync" }'

A 202 Accepted confirms the sync was launched (it runs asynchronously). Only one sync runs at a time — if a sync is already in progress the call returns 409; wait for the running sync to finish, then retry. See the REST API guide for the full contract.

6. Read its state

The resource's computed attributes are managed by Lumos — read them from state rather than setting them. The two most useful are disconnected (whether the integration's credentials have been removed) and instance_id (the tenant/instance Lumos resolved for the connection). Expose them with an output:

output "okta_connected" {
  value = !lumos_app.okta.disconnected
}
terraform apply                      # refreshes computed attributes
terraform output okta_connected      # e.g. true
terraform state show lumos_app.okta  # full attribute set

For an OAuth or Merge app, apply starts the connection but the authorization is finished in the Lumos UI — until you complete it there, the integration isn't syncing yet. Complete the consent in the Lumos UI, and manage the resource from Terraform as usual afterwards:

resource "lumos_app" "slack" {
  app_class_id = "slack.com"

  auth = jsonencode({
    client_id     = var.slack_client_id
    client_secret = var.slack_client_secret
  })
  # apply starts the connection; complete OAuth consent in the Lumos UI.
}

7. Update an integration

Precondition: the integration already exists in state.

Change auth (for example to rotate an API key) or settings, then apply:

resource "lumos_app" "okta" {
  app_class_id = "okta.com"

  auth = jsonencode({
    api_key = var.okta_api_token_rotated
  })
}
terraform apply

Changing app_class_id replaces the integration (Terraform destroys and recreates it). If a sync is already running for the integration, the update is throttled with HTTP 429 and a Retry-After hint — wait and re-apply.

8. Import an existing integration

Precondition: declare a matching lumos_app resource block first. You need the integration's id (the UUID returned when you connect it via the Integrations API, or listed by GET /v1/apps?connection_source=API).

terraform import lumos_app.okta 7d3b1c2e-8f4a-4c1d-9b2e-1a2b3c4d5e6f

Then run terraform plan to confirm your HCL matches the imported state, and fill in auth/settings as needed.

9. Destroy it

Removing the resource (or running terraform destroy) disconnects the integration and removes its stored credentials. The app remains available to reconnect later.

terraform destroy -target=lumos_app.okta

Argument reference

These are the arguments you set when declaring an integration. Their meanings match the field reference in Managing Integrations via API; discover the exact keys each integration accepts via its connection schema (step 1).

ArgumentWhat it isRequiredConstraints
app_class_idIdentifier of the app to connect, e.g. okta.com.RequiredMust be an app available to your domain. Changing it forces replacement of the resource.
authThe app's credentials as a JSON object, written with jsonencode({...}). All secrets go here.OptionalKeys depend on the app (for example api_key, or client_id and client_secret); validated against the app's connection schema. Mark the variables you feed it as sensitive. May be empty for apps you finish authorizing in the UI.
settingsNon-secret configuration as a JSON object, written with jsonencode({...}) (for example region, host, or tenant/instance identifier).OptionalKeys depend on the app; the connection schema lists them. Apps whose tenant is set manually take it here; others derive it from the credentials.
versionPins the integration to a specific version.OptionalLeave unset to use the default.

Read-only attributes

Lumos computes these; reference them but don't set them:

  • id — the integration's UUID (use it with terraform import and the REST sync endpoint).
  • instance_id — the tenant/instance Lumos resolved for the connection (either the value you configured in settings or one derived from the credentials).
  • disconnectedfalse while the integration's credentials are stored (connected); true after a disconnect.

Handling errors

The provider surfaces API failures as Terraform diagnostics during plan or apply, formatted HTTP <code>: <detail>. Common ones:

FailureCauseWhat to do
Missing Provider Security ConfigurationNo token.Export LUMOS_ACCESS_TOKEN (or set http_bearer).
HTTP 400Missing or invalid credentials, an unknown app_class_id, or a field the app doesn't support.Fix auth/app_class_id; confirm the app's required keys via its connection schema.
HTTP 401Invalid API token.Check LUMOS_ACCESS_TOKEN.
HTTP 403The token's user can't manage integrations.Use a token for an admin with integration permissions.
HTTP 404The integration no longer exists.On refresh, Terraform drops it from state — re-apply to recreate, or remove the block.
HTTP 409An integration for this app instance already exists.terraform import it instead of creating a new one.
HTTP 429A sync is already running for this integration.Wait for the Retry-After interval, then re-apply.
HTTP 502The app rejected the credentials, or the sync failed.Verify the credentials with the provider, then re-apply.

Next steps

  • Expose disconnected (and other attributes) as outputs to monitor your integrations.
  • Rotate credentials on a schedule by updating auth and re-applying — ideally from CI.
  • For the underlying field semantics and the discovery endpoints, see Managing Integrations via API and the full reference at https://developers.lumos.com.


Did this page help you?