Managing Integrations via API

The Integrations API lets you connect and manage your app integrations programmatically - the same connections you'd otherwise set up by hand in the Lumos UI. Use it to wire up integrations from a script, a CI pipeline, or an infrastructure-as-code tool.

Overview

An integration is a connection between Lumos and one of your apps (for example Okta or Google Workspace). Once connected, Lumos syncs that app's accounts and entitlements so you can review and manage access. With this API you can browse the catalog of integrations you can connect, discover what each one needs to connect, connect one by supplying its credentials, trigger a sync on demand, reconnect one to rotate its credentials, and disconnect one.

To see the integrations you've already connected (for example to reconcile state from Terraform), use the Apps API: GET /v1/apps?connection_source=API lists the integrations you connected through this API.

Before you start

You need:

  • An API token. Create one in the Lumos UI under Settings β†’ API Tokens. The token is shown once - copy it somewhere safe. Examples below read it from a LUMOS_ACCESS_TOKEN environment variable.
  • Permission to manage integrations. A token acts as the user who created it, so that user must be an admin with permission to connect and remove integrations. Without it, calls return 403.
  • The base URL https://api.lumos.com. All paths below are relative to it.

Some apps (those that use OAuth or the Merge widget) can't be fully connected from the API alone - the API call starts the connection and you finish authorizing it in the Lumos UI. See Connect an integration.

Authentication

Send your token as a bearer token in the Authorization header on every request:

curl -s https://api.lumos.com/v1/integrations \
  -H "Authorization: Bearer $LUMOS_ACCESS_TOKEN"

A request with a missing or invalid token returns 401.

Concept-to-endpoint map

Use this table to jump from what you're trying to do to the right endpoint.

I want to...EndpointNotes
Browse integrations I can connectGET /v1/integrationsCatalog metadata, keyed by app_class_id. Paginated; filter with ?query=.
Look up one connectable integrationGET /v1/integrations/{app_class_id}Catalog metadata for a single app_class_id.
See what an integration needs toconnectGET /v1/integrations/{app_class_id}/connection-schemaJSON schema of the auth and settings to supply.
Connect a new integrationPOST /v1/appsPut the app's credentials in auth. Returns the integration's id.
Update credentials or fieldsPUT /v1/apps/{id}"Reconnect" (also triggers a sync). Uses the id from connect.
Trigger a sync on demandPOST /v1/apps/{id}/syncRuns a full sync. 409 while one is already running.
Disconnect an integrationDELETE /v1/apps/{id}Removes stored credentials. Uses the id from connect.
List integrations connected via this APIGET /v1/apps?connection_source=APIThe Terraform read path; returns each connected integration in the App shape.

Core workflow

1. Discover what you can connect

Before connecting anything, find the integration's app_class_id and learn what it needs to connect.

Browse or search the catalog of connectable integrations:

curl -s "https://api.lumos.com/v1/integrations?query=okta" \
  -H "Authorization: Bearer $LUMOS_API_TOKEN"
{
  "items": [
    {
      "app_class_id": "okta.com",
      "name": "Okta",
      "description": "Okta is a management platform that secures critical resources from cloud to ground for workforce and customers.",
      "category": "IT & Security",
      "logo_url": "https://..."
    }
  ],
  "total": 1,
  "page": 1,
  "size": 50,
  "pages": 1
}

The app_class_id is the canonical identifier you'll pass when connecting. The list is paginated (?page=, ?size=) and ?query= filters by identifier or name.

Check the connection schema to see exactly which credentials (auth) and configuration (settings) the integration takes β€” and where each value goes:

curl -s "https://api.lumos.com/v1/integrations/okta.com/connection-schema" \
  -H "Authorization: Bearer $LUMOS_API_TOKEN"
{
  "app_class_id": "okta.com",
  "auth_schema": {
    "type": "object",
    "properties": {
      "api_key": { "type": "string", "title": "API key", "writeOnly": true }
    },
    "required": ["api_key"]
  },
  "settings_schema": {
    "type": "object",
    "properties": {
      "app_instance_identifier": { "type": "string", "title": "Domain" }
    },
    "required": ["app_instance_identifier"]
  }
}

Read it as: put every key from auth_schema in the request's auth object (secret fields are marked writeOnly) and every key from settings_schema in settings. Here, Okta takes an api_key credential and its tenant domain as settings.app_instance_identifier. Other integrations use different keys β€” some name the tenant differently (e.g. sub), some derive it from the credentials and need no setting at all, and some nest auth per authentication method when the integration supports several. An empty auth_schema means no credentials are supplied up front: the connection is completed via browser consent in the Lumos UI.

2. Connect an integration

Precondition: the app_class_id and connection schema from step 1, and valid credentials for the app.

Send the identifier in app_class_id, credentials in auth, and non-secret configuration in settings β€” shaped exactly as the connection schema describes:

curl -s -X POST https://api.lumos.com/v1/apps \
  -H "Authorization: Bearer $LUMOS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "app_class_id": "okta.com",
    "auth": { "api_key": "your-okta-api-token" },
    "settings": { "app_instance_identifier": "your-org.okta.com" }
  }'

A 201 Created response returns the connected integration in the same App shape as the rest of the API. When the credentials are complete, the first sync starts automatically:

{
  "id": "7d3b1c2e-8f4a-4c1d-9b2e-1a2b3c4d5e6f",
  "app_class_id": "okta.com",
  "instance_id": "your-org.okta.com",
  "user_friendly_label": "Okta",
  "disconnected": false
}

(Response trimmed to the relevant fields β€” the full App shape also carries catalog metadata such as category, description, and logo_url.)

Save the id - you'll use it to look up, sync, reconnect, or disconnect the integration. instance_id is the tenant/instance Lumos resolved for the connection β€” either the value you configured or one derived from the credentials.

OAuth and Merge apps connect in two steps. For those, the API call starts the connection but doesn't complete it β€” finish authorizing the connection in the Lumos UI. No sync runs until you do.

3. Configure its fields

You set an integration's fields when you connect it: credentials in auth and non-secret configuration in settings (see the field reference).

Precondition: the integration id from step 2.

To change those fields later β€” for example to rotate an API key β€” send the new values to PUT /v1/apps/{id}. The app_class_id must match the existing integration:

curl -s -X PUT https://api.lumos.com/v1/apps/$INTEGRATION_ID \
  -H "Authorization: Bearer $LUMOS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "app_class_id": "okta.com",
    "auth": { "api_key": "your-new-okta-api-token" }
  }'

A 200 OK response returns the integration in the same shape as connect. If a sync is already running for the integration, the reconnect is throttled with 429 and a Retry-After header (seconds) β€” wait that long and retry.

4. Trigger a sync

Lumos starts a sync automatically whenever a connect or reconnect completes successfully. To run a sync on demand afterwards β€” for example from a scheduled job, or right after Terraform connects the integration β€” call POST /v1/apps/{id}/sync with the sync_type you want. Today the only supported value is full_sync (a full re-pull of accounts, groups, and entitlements):

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

Precondition: the integration is connected (or its last sync failed β€” retrying after a failure is allowed).

The sync runs asynchronously, so a 202 Accepted confirms it was launched β€” it does not wait for the sync to finish:

{
  "app_id": "7d3b1c2e-8f4a-4c1d-9b2e-1a2b3c4d5e6f",
  "sync_type": "full_sync",
  "status": "SYNCING"
}

Only one sync runs at a time. A few cases to handle:

  • 409 Conflict β€” a sync is already in progress. Wait for the running sync to finish, then retry. A 409 is also returned when the integration isn't connected yet, or when its first sync is deferred pending an admin review in the Lumos UI.
  • 422 Unprocessable Entity β€” manual/CSV apps can't be synced this way.

5. List what you've connected

The Apps API is the read path. Fetch a single integration by the id you saved
from connect, or list everything you connected through this API. Responses never
return credentials.

curl -s "https://api.lumos.com/v1/apps/$INTEGRATION_ID" \
  -H "Authorization: Bearer $LUMOS_API_TOKEN"

Each integration is returned in the same App shape as connect (trimmed here):

{
  "id": "7d3b1c2e-8f4a-4c1d-9b2e-1a2b3c4d5e6f",
  "app_class_id": "okta.com",
  "instance_id": "your-org.okta.com",
  "disconnected": false
}

disconnected: false means the integration is connected (its credentials are stored); after you disconnect it, it reads true.

To list every integration you connected through this API at once, use the
connection_source filter:

curl -s "https://api.lumos.com/v1/apps?connection_source=API" \
  -H "Authorization: Bearer $LUMOS_API_TOKEN"

6. Disconnect

Disconnecting removes the integration's stored credentials.

Precondition: the integration id.

curl -s -X DELETE https://api.lumos.com/v1/apps/$INTEGRATION_ID \
  -H "Authorization: Bearer $LUMOS_API_TOKEN"

A 200 OK response confirms the disconnect β€” the app is no longer connected but
remains available to reconnect later:

{
  "id": "7d3b1c2e-8f4a-4c1d-9b2e-1a2b3c4d5e6f",
  "app_class_id": "okta.com",
  "disconnected": true
}

Field reference

These are the fields you provide when connecting or reconnecting an integration
(POST / PUT). Discover the exact keys each integration accepts via
GET /v1/integrations/{app_class_id}/connection-schema.

FieldWhat it isRequiredConstraints
app_class_idIdentifier of the app to connect, e.g. okta.com.RequiredMust be an app available to your domain. An unknown value returns 400; omitting it returns 422.
authObject holding the app's credentials. All secrets go here.Required for most appsKeys depend on the app (for example api_key, or client_id and client_secret); the connection schema lists them. May be empty for apps you finish authorizing in the UI. Wrong or missing credentials return 400; credentials the app rejects return 502.
settingsObject of non-secret configuration (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 (for Okta, as settings.app_instance_identifier); others derive it from the credentials. Defaults to empty.
versionPins the integration to a specific version.OptionalLeave unset to use the default, which is correct for almost all integrations.

What you get back

Responses never include credentials. Connect/reconnect return the connected
integration in the same App shape as the rest of the API β€” most relevantly
id, app_class_id, instance_id (the resolved tenant/instance), and
disconnected β€” plus catalog metadata. The Apps API (GET /v1/apps) returns
the same shape.

Handling errors

Errors return the relevant HTTP status and a JSON body with a human-readable
detail, plus machine-readable type and code fields your automation can
branch on:

{
  "detail": "Integration datadog.com not found.",
  "type": "not_found",
  "code": "not_found",
  "errors": []
}
StatusCauseWhat to do
400 Bad RequestMissing or invalid credentials, an unknown app_class_id, or a field the app doesn't support.Fix the request body; confirm the required auth keys via the connection schema.
401 UnauthorizedMissing or invalid API token.Send a valid Authorization: Bearer token.
403 ForbiddenYour user can't manage integrations.Use a token for an admin with integration permissions.
404 Not FoundNo integration with that id (or app_class_id) in your domain.Check the id from the connect or list response.
409 ConflictAn integration for this app instance already exists; or (on sync) a sync is already running or the app isn't in a syncable state.Reconnect the existing integration instead of connecting a new one; for syncs, wait for the running sync to finish and retry.
422 Unprocessable EntityThe request body failed validation (for example app_class_id is missing), or the app can't be synced via the API (manual/CSV).Provide all required fields in the correct shape.
429 Too Many RequestsA sync is already running while you reconnect.Wait and retry; honor the Retry-After header (seconds).
502 Bad GatewayThe app rejected the credentials, or the sync failed.Verify the credentials with the provider, then retry.

Next steps

  • Browse the integrations you can connect with GET /v1/integrations, and check
    each one's connection-schema before connecting.
  • Audit what you've connected through this API with GET /v1/apps?connection_source=API.
  • Rotate credentials on a schedule by reconnecting with PUT /v1/apps/{id}.
  • See the full API reference at https://developers.lumos.com.


Did this page help you?