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/lumosprovider (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_TOKENenvironment 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 connect | The REST discovery endpoints — see step 1. |
| Connect a new integration | Declare a lumos_app resource, then terraform apply. |
| Set credentials and configuration | auth and settings arguments (as jsonencode({...})). |
| Read an integration's state | The resource's computed attributes, e.g. disconnected and instance_id (via an output or terraform state show). |
| Rotate credentials / change fields | Edit auth/settings and terraform apply. |
| Adopt an integration that already exists | terraform import. |
| Disconnect an integration | Remove 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 integrationWhen 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 setFor 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 applyChanging 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-1a2b3c4d5e6fThen 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.oktaArgument 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).
| Argument | What it is | Required | Constraints |
|---|---|---|---|
app_class_id | Identifier of the app to connect, e.g. okta.com. | Required | Must be an app available to your domain. Changing it forces replacement of the resource. |
auth | The app's credentials as a JSON object, written with jsonencode({...}). All secrets go here. | Optional | Keys 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. |
settings | Non-secret configuration as a JSON object, written with jsonencode({...}) (for example region, host, or tenant/instance identifier). | Optional | Keys depend on the app; the connection schema lists them. Apps whose tenant is set manually take it here; others derive it from the credentials. |
version | Pins the integration to a specific version. | Optional | Leave 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 withterraform importand the REST sync endpoint).instance_id— the tenant/instance Lumos resolved for the connection (either the value you configured insettingsor one derived from the credentials).disconnected—falsewhile the integration's credentials are stored (connected);trueafter a disconnect.
Handling errors
The provider surfaces API failures as Terraform diagnostics during plan or apply, formatted HTTP <code>: <detail>. Common ones:
| Failure | Cause | What to do |
|---|---|---|
Missing Provider Security Configuration | No token. | Export LUMOS_ACCESS_TOKEN (or set http_bearer). |
HTTP 400 | Missing 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 401 | Invalid API token. | Check LUMOS_ACCESS_TOKEN. |
HTTP 403 | The token's user can't manage integrations. | Use a token for an admin with integration permissions. |
HTTP 404 | The integration no longer exists. | On refresh, Terraform drops it from state — re-apply to recreate, or remove the block. |
HTTP 409 | An integration for this app instance already exists. | terraform import it instead of creating a new one. |
HTTP 429 | A sync is already running for this integration. | Wait for the Retry-After interval, then re-apply. |
HTTP 502 | The 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
authand 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.
Updated about 23 hours ago