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_TOKENenvironment 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... | Endpoint | Notes |
|---|---|---|
| Browse integrations I can connect | GET /v1/integrations | Catalog metadata, keyed by app_class_id. Paginated; filter with ?query=. |
| Look up one connectable integration | GET /v1/integrations/{app_class_id} | Catalog metadata for a single app_class_id. |
See what an integration needs toconnect | GET /v1/integrations/{app_class_id}/connection-schema | JSON schema of the auth and settings to supply. |
| Connect a new integration | POST /v1/apps | Put the app's credentials in auth. Returns the integration's id. |
| Update credentials or fields | PUT /v1/apps/{id} | "Reconnect" (also triggers a sync). Uses the id from connect. |
| Trigger a sync on demand | POST /v1/apps/{id}/sync | Runs a full sync. 409 while one is already running. |
| Disconnect an integration | DELETE /v1/apps/{id} | Removes stored credentials. Uses the id from connect. |
| List integrations connected via this API | GET /v1/apps?connection_source=API | The 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. A409is 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.
| Field | 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. An unknown value returns 400; omitting it returns 422. |
auth | Object holding the app's credentials. All secrets go here. | Required for most apps | Keys 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. |
settings | Object of non-secret configuration (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 (for Okta, as settings.app_instance_identifier); others derive it from the credentials. Defaults to empty. |
version | Pins the integration to a specific version. | Optional | Leave 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": []
}| Status | Cause | What to do |
|---|---|---|
400 Bad Request | Missing 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 Unauthorized | Missing or invalid API token. | Send a valid Authorization: Bearer token. |
403 Forbidden | Your user can't manage integrations. | Use a token for an admin with integration permissions. |
404 Not Found | No integration with that id (or app_class_id) in your domain. | Check the id from the connect or list response. |
409 Conflict | An 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 Entity | The 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 Requests | A sync is already running while you reconnect. | Wait and retry; honor the Retry-After header (seconds). |
502 Bad Gateway | The 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'sconnection-schemabefore 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.
Updated about 23 hours ago