> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lumos.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 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. For an example that configures AppStore requestable permissions with the same provider, see [Terraform](/docs/terraform) in the Developers tab.

## 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 provides 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](#6-read-its-state).

## Concept-to-operation map

| I want to... | How |
| - | - |
| Find what an integration needs to connect | The REST discovery endpoints — see [step 1](#1-discover-the-integration-and-its-connection-schema). |
| 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:

```hcl theme={null}
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.
}
```

```bash theme={null}
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:

```bash theme={null}
# 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](/integrations/managing/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`:

```hcl theme={null}
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:

```hcl theme={null}
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](#argument-reference) for every field.

### 4. Initialize, plan, and apply

**Precondition:** `LUMOS_ACCESS_TOKEN` is exported.

```bash theme={null}
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`:

```bash theme={null}
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](/integrations/managing/api) 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`:

```hcl theme={null}
output "okta_connected" {
  value = !lumos_app.okta.disconnected
}
```

```bash theme={null}
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:

```hcl theme={null}
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`:

```hcl theme={null}
resource "lumos_app" "okta" {
  app_class_id = "okta.com"

  auth = jsonencode({
    api_key = var.okta_api_token_rotated
  })
}
```

```bash theme={null}
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](/integrations/managing/api), or listed by `GET /v1/apps?connection_source=API`).

```bash theme={null}
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.

```bash theme={null}
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](#1-discover-the-integration-and-its-connection-schema)).

| 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 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).
* `disconnected` — `false` 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:

| 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 `auth` and re-applying — ideally from CI.
* For the underlying field semantics and the discovery endpoints, see [Managing Integrations via API](/integrations/managing/api) and the full reference at [API reference](/reference/lumos-api).

<br />
