Skip to main content
An app is one connected instance of an integration in your organization. github.com is an integration. When your organization connects GitHub, the result is an app. If you connect the same integration twice, for example to a production tenant and to a sandbox, you have two apps, and instance_id tells them apart. Almost everything else in v1 belongs to an app. Its entitlements are the units of access you can grant on it. Its accounts are the user, role, and service accounts that exist on it. The three workflows bound to it decide what Lumos does when someone requests access to it.
The App object

Attributes

Absent and null fields

The response omits fields that have no value instead of returning them as null. An app with no description has no description key at all, so read the object with .get() semantics rather than expecting every field in the table above. Three exceptions break that rule on purpose:
  • The response includes the five *_override fields as null when they aren’t set.
  • The response includes the three workflow fields as null when no workflow is bound. It omits one only when you can’t read it.
  • The response omits appstore_settings entirely when you can’t read it, instead of returning it empty.
The last two let you distinguish “this app has no approval workflow” from “you can’t see this app’s approval workflow.” Everywhere else, an absent field has no value, or you didn’t name it in expand. An expanded appstore_settings keeps its own fields that have no value, as null. It matches what Get AppStore Settings returns.

Display fields

label, logo_url, website_url, category, and description are what people see. Each one shows its *_override field when your organization set one, and the app’s default otherwise. They’re read-only, so a create or update body that sends one returns 400. Write the *_override fields instead, as Update metadata or credentials describes. The overrides change only your organization’s copy of the app, not the shared Lumos catalog. Lumos stores each one exactly as you send it, even when it matches the default, so a later change to the default doesn’t change what you read back.

Bound workflows

Every app has three workflow fields: approval_workflow, default_provisioning_workflow, and default_deprovisioning_workflow. Each holds a reference to the bound workflow, or null when none is bound.
Name a workflow field in expand to get the full workflow definition inline instead of the reference. Each workflow field has its own permission check. Of the built-in roles, Admin, Read Only Admin, and AppStore Read Only Admin can read all three fields on every app, and App Admin can read them on the apps they administer. A workflow field you can’t read is absent, as Absent and null fields describes. To read or change which workflow is bound, use that field’s own workflow endpoints, listed under Endpoints, rather than sending the whole app object.

Expanding fields with expand

Reads return the base object. Pass expand to include fields that reads omit by default:
Six fields are expandable: admins, approval_workflow, default_provisioning_workflow, default_deprovisioning_workflow, custom_attributes, and appstore_settings. A field you can’t read is omitted rather than failing the request, as Requesting fields with expand describes.

admins

admins is an array of user and group references. Expanding it on an app with no admins returns an empty array, not a missing key.
Get App Admins returns the same list on its own. To change it, send the complete list to Set App Admins. Anyone you omit stops being an admin, and an empty list removes every admin. A user or group outside your organization returns 422 with code set to user.not_found or group.not_found. An app needs at least one admin before you add it to the AppStore. Setting in_app_store to true on an app with no admins returns 409 with code set to app.admin_required, so set the admins first. Removing the last admin doesn’t take a listed app out of the AppStore.

custom_attributes

custom_attributes is an object keyed by attribute label. Each value carries its own type, one of text, user, or single-select. An app with no custom attributes configured may omit the key even when you expand it.

appstore_settings

appstore_settings contains only in_app_store unless you expand it. Expanded, it returns the full settings object that AppStore settings describes. Get AppStore Settings returns the same fields on their own. Use it when you need only the settings.

AppStore settings

Update AppStore Settings is a merge patch that takes these fields. Every field is optional, and a field you omit keeps its value. null clears a field, except for four that always hold a value: in_app_store, catalog_visibility, allow_permission_selection, and allow_multiple_permission_selection. null on one of them returns 400. A value you send for a list field replaces the whole list.

Time-based access

time_based_access has two fields:
A label must match one of your organization’s options exactly, or the call returns 422 with code set to time_based_access.unknown_option. New organizations start with options such as 7 days, 30 days, and Unlimited. Your organization’s default options are the subset of its options that an app offers when it has no list of its own. Sending options pins that list on the app, so later changes to your organization’s default options no longer apply to it. Send default together with options. While the app uses your organization’s default options, a default sent alone returns 422 with code set to time_based_access.default_not_offered. To return to your organization’s default options, send both as null. When you read the settings, options is null while the app uses your organization’s default options. Otherwise each option carries a label and a value_seconds, which is null for a duration with no end.

Custom intake fields

Each item in custom_intake_fields describes one question:
Send the whole list each time, up to 50 fields. Lumos matches fields by reference_key, updates the ones you send, and deletes the ones you omit. null or an empty list deletes them all. A list that breaks these rules returns 400 with code set to appstore_settings.custom_intake_fields_invalid. errors lists every problem by position, such as custom_intake_fields[2].reference_key. 409 means another call used the same reference_key at the same time.

Entitlement selection

allow_permission_selection decides whether requesters pick an entitlement:
  • With true, they choose from the app’s visible entitlements, and allow_multiple_permission_selection decides whether they can choose more than one.
  • With false, they can’t choose. A request gets the app’s default_entitlement, or no entitlement when none is set.
default_entitlement must be one of this app’s entitlements, and it must have AppStore settings. Otherwise the call returns 422 with code set to entitlement.wrong_app or entitlement.not_requestable. Entitlements from Create Entitlement always have them.

Request validation

For request_validation_webhook_uuid, use the id of an inline webhook whose type is request-validation, from List Inline Webhooks. A webhook of another type returns 422 with code set to request_validation_webhook.wrong_type. The app’s webhook checks requests that don’t name an entitlement. For a request that names entitlements, Lumos calls each entitlement’s own webhook instead of the app’s, so set a webhook on each entitlement too, as AppStore settings on the Entitlement object describes.

Syncs and their status

sync_status reports the state of the app’s most recent sync, not the health of the app’s credentials. It moves from syncing to success or failed. sync_status is absent when Lumos has no sync result for the app, for example because the app has never synced or was disconnected. An absent sync_status doesn’t mean that a sync failed. Trigger App Sync starts a sync and returns 202 with an operation instead of waiting for the sync to finish. sync_type is required, and full-sync is the only value it accepts today. Any other value returns 400.
Response
Poll Get Operation with the operation’s id until done is true, as Long-running operations describes. At that point the operation carries result if the sync succeeded, or error with code set to app_sync.failed if it failed. The operation is a view of the app’s latest sync rather than a record of one run, and it reads the same state as sync_status. Only one sync runs per app at a time. If you request a sync while another sync is running on the app, Lumos returns 409 with code set to app.sync_in_progress. Lumos doesn’t queue the second request.

The first sync

A newly connected app’s first sync waits for a review of its identity matching rules, which decide the Lumos user that each of the app’s accounts belongs to. Without custom rules, Lumos matches accounts to users by email address. Until that sync starts, the app has no synced accounts or entitlements, and Trigger App Sync without skip_first_sync_review returns 409. Of the built-in roles, only Admin can start that sync, and a custom role can also allow it. Start the first sync in one of two ways:
  • In the Lumos UI, after reviewing the rules.
  • With Trigger App Sync and skip_first_sync_review: true, which starts it without the review.
The flag has no effect once the app has completed a sync successfully. If the first sync fails, the next one waits for review again, so send the flag again when you retry. If you send credentials to an app that has never completed a sync, the sync that starts also waits for review.

Creating, updating, disconnecting, and deleting apps

Each of these four operations has its own endpoint:
  • Create or connect. Create App returns 201 with the new app.
  • Update or reconnect. Update App returns 200 with the updated app. Sending credentials reconnects it.
  • Disconnect. Disconnect App removes the stored credentials and returns 200 with the app’s id, its app_class_id, and disconnected: true. Reconnect it later by sending credentials to Update App.
  • Delete. Delete App removes the stored credentials in every case. For a custom app, it deletes the app together with its AppStore settings, admins, and custom attributes, and returns 204. If the app still has accounts, or other Lumos data still references it, the call returns 409 with code set to app.delete_blocked, and the app stays in a disconnected state. An app connected through an integration is disconnected during the call, which returns 202 with an operation. Lumos then deletes its record in the background.
Disconnect and delete differ in what they promise. Disconnect also deletes the record once the app has no accounts left and nothing else references it, which can happen after the call returns. It returns 200 either way. Delete tells you which happened. For a custom app, 204 means the app is gone, and 409 means it’s still readable in a disconnected state. detail says whether accounts or other data kept it. After you remove its accounts, Delete can return 409 for a short time, until Lumos updates the app’s account count. For an app connected through an integration, poll the operation until done is true, as Long-running operations describes. Disconnect, delete, and credential updates work only on apps connected through the API. List Apps returns these apps for connection_source=api. Any other app returns 409. An app created in the Lumos UI stays managed in the UI, so automation can’t overwrite its credentials. Metadata updates work on any app.

Create a custom app or connect an integration

Create App takes one of two bodies:
  • Connect an integration with app_class_id, the credentials in auth, and any non-secret configuration in settings. The keys vary by integration, so read them from Get Integration Connection Schema rather than hardcoding them. Credentials that validate right away connect the app. Its first sync then waits for a review of the app’s identity matching rules, or for you to start it without the review, as The first sync describes.
  • Create a custom app with label_override, category_override, and description_override, and either no app_class_id or app_class_id set to custom. A custom app is an app that Lumos has no integration for, so you maintain its accounts yourself, for example with Upload Accounts. It reports app_class_id as custom, so you can send back a body you read.
Either body can also set logo_url_override, website_url_override, request_instructions, and post_approval_message, and connecting an integration can set the other overrides too. A body that sends auth, settings, or version without an integration’s app_class_id returns 400. For an integration that needs browser consent, send auth: {} and finish connecting it in the Lumos UI. The API can’t connect the integrations below at all, and returns 422 with code set to app.api_unsupported for them:
  • a CSV/flatfile app
  • an integration whose auth or settings fields the API doesn’t support yet
An on-prem integration that none of your connected on-prem agents run returns 409 with code set to app.on_prem_connector_unavailable. Install the connector on an agent, then retry.
Create a custom app

Update metadata or credentials

Update App treats two groups of fields differently:
  • Metadata (label_override, category_override, description_override, logo_url_override, website_url_override, request_instructions, post_approval_message) is merge-patched. An omitted field keeps its value.
    • null clears an override, so the matching display field shows the app’s default again. A custom app has no default name, description, or category, so null for one of those three returns 422 with code set to app.field_not_clearable.
    • An override can’t be empty. label_override takes up to 255 characters, description_override up to 1,000, and the two URLs up to 2,048.
    • request_instructions ignores null, so this endpoint can’t clear it.
    • post_approval_message takes up to 511 characters. It has no default, so null removes it, and an app with no message omits the field. "" returns 400.
  • Credentials (auth, settings, version) are one group. Lumos replaces the group instead of merging it, so auth is required whenever you send settings or version. A body without auth returns 400, and the stored credentials don’t change. Sending any of the three reconnects the app with exactly what you sent. Lumos re-validates the credentials and starts a sync. If the app has never completed a sync, that sync waits for review, as The first sync describes. For an integration that needs browser consent, the reconnect waits for that consent in the Lumos UI.
If the app’s sync started in the last few minutes, sending credentials returns 409 with code set to app.sync_in_progress. The Retry-After header gives the wait in seconds.

Reconciling apps connected through the API

GET /v1/apps takes a disconnected filter, because a disconnected app can stay readable. Pair disconnected=false with connection_source=api to reconcile only the apps connected through the API:

AppStore catalog view

Get AppStore Catalog is the requester view of your AppStore, and it deliberately returns less than the object above: no workflow fields, no post_approval_message, no admins, no custom_attributes, and no sync_status. Its appstore_settings is scoped to what a requester needs to see. Don’t write code that treats the two shapes as interchangeable. If you need the admin view of an app, fetch it by ID.

Endpoints

Every app ID in these paths is the id field of the object above.