Skip to main content
A condition is a JSON object that Lumos checks against one identity to decide whether the identity matches. A user in your organization is one kind of identity. Two v1 fields take a condition: Both fields use the same operators and attributes. This is the smallest useful condition:
It matches identities whose team is Engineering.

How a condition is built

A condition is either a rule or a combination of conditions. A rule names one attribute, one operator, and the value to compare against, like the example above. Put exactly one operator in each rule. A rule with two operators returns 400. Combine conditions with and and or. Each takes a list of at least one condition, and combinations can nest.
Negate with not. These two forms mean the same thing. The second is the one the condition builder in Lumos writes.
A value is a string, a number, a boolean, or a date-time, or a list of them for the operators that take a list. Any value, and attribute itself, can instead be an object with a label and a value. Lumos compares value and keeps label for display.

What Lumos rejects

Lumos checks a condition when you save it, and returns 400 for a condition it can’t evaluate:
  • A key it doesn’t recognize, such as a misspelled operator.
  • A rule with more than one operator.
  • An attribute that isn’t in Attributes, including a misspelled path or a custom attribute your organization has deleted.
  • An operator that isn’t offered for the attribute’s type, or one that isn’t offered negated.
  • A date-time without a time zone, as Values describes.
An unrecognized key returns code validation_error on an Access Policy and bad_request on a workflow. Every other problem returns bad_request, and detail names each problem. On a workflow, detail starts with the branch, such as branches[0].condition. Apart from date-times, Lumos doesn’t check values, so a misspelled group ID or status saves, and equals on it never matches. A condition saved in Lumos before an attribute was deleted fails the same check, so an update that sends it back returns 400. Patch Access Policy without access_condition keeps the stored condition.

Stored form

Lumos returns a condition in its stored form. In that form, attribute and every value are objects with a label and a value, and Lumos adds the labels you didn’t send.
You send
You read back
Both forms evaluate the same way, so you can send the stored form unchanged. To tell whether a stored condition is the one you wrote, compare the value fields and ignore the labels.

Operators

Offered for lists the attribute types each operator is offered for. Pair each attribute with an operator offered for its type. Any other pairing returns 400. Every operator except equals, !=, >, >=, <, and <= is offered negated with not. Each of those six has an opposite operator, so it isn’t offered negated, and negating one returns 400. Write != rather than negating equals, and <= rather than negating >. exists works either way, because "exists": false and "not": { "exists": true } match the same identities. Text comparisons are case-sensitive, so "ends_with": "@acme.com" doesn’t match Jane@ACME.com.

contains, in, contains_any, and is_subset_of

These four names sound alike, but the operators behave differently. contains matches part of the attribute’s text. contains_any compares whole values, so a rule asking for the group app-dropbox-read doesn’t match an identity in the group app-dropbox-readwrite. Give in a list. Given a single string, in matches when the attribute’s value appears inside that string. For example, {"attribute": "identity.team", "in": "Engineering"} also matches a team named Eng, and a team whose value is an empty string. is_subset_of is the “every value” counterpart to contains_any. An attribute with no value never matches it, even against an empty list. No attribute on this page offers it, so a rule that uses it returns 400.

Attributes

An attribute is a path into the identity being checked. These are the paths Lumos offers: In a rule, use the group’s integration_specific_id, not its id. An integration_specific_id is unique only within the app that syncs the group. If two apps sync groups that have the same integration_specific_id, a rule that names that ID matches the members of both groups. In a workflow branch step, identity.source_app.id never has a value. Rules on it work only in Access Policies.

Custom attributes

Every custom attribute your organization defines on users is available by its label. The path depends on the custom attribute’s type: Custom attributes of other types aren’t offered. Inside a JSON string, escape the double quotes around the label ("identity.custom_attributes[\"Department\"]") or use single quotes ("identity.custom_attributes['Department']"). Both work, and the examples on this page use single quotes. To find a label, call List Custom Attribute Definitions with config_type=user. The label field is what goes in the path. The Custom Attribute Definition object covers the other fields. To see one identity’s values, call GET /v1/users/{user_id}?expand=custom_attributes. Its custom_attributes map is keyed by the same labels. identity.team, identity.title, identity.start_date, and identity.end_date have a value only for identities that hold the matching custom attribute. To write a rule on an identity’s manager, use identity.custom_attributes['Manager'].id, which is offered as a string, rather than identity.manager.uuid.

Values

  • Date-times are ISO 8601 strings with a time zone, such as 2025-01-01T00:00:00Z.
  • Booleans are JSON true and false. The string "true" never equals true.
  • Numbers and text compare loosely in equals and in, so 5030 matches "5030".
A date-time without a time zone, such as 2025-01-01 or 2025-01-01T00:00:00, returns 400, and so does a value that isn’t ISO 8601. exists takes true or false, so this doesn’t apply to it.

Missing values

An attribute has no value when the identity has nothing for it, such as no team or no end date. A list with no items counts as no value too, so an identity in no groups has no value for identity.groups[*].external_id. An empty string is a value. exists tests for this. true matches identities that have a value, and false matches those that don’t. Every other operator treats a missing value as a mismatch, except != and negated operators, which match it: A rule for “everyone not in Sales” includes identities with no team. To exclude them, add an exists rule with and:

Examples

Department is Engineering

Matches identities whose Department custom attribute is exactly Engineering. An identity with no department doesn’t match.

Member of any of these groups

Matches any identity in at least one of the two groups. Replace the IDs with each group’s integration_specific_id from GET /v1/groups.

Everyone

Don’t write this condition yourself. On an Access Policy, set is_everyone_condition to true, and Lumos stores this condition:
On an Access Policy, you can also send this condition back as access_condition, and Lumos reads it as is_everyone_condition: true. A workflow branch step rejects it, because identity.stable_identifier isn’t an attribute it offers.

Everyone except contractors

Matches every identity outside the contractors group, including identities in no groups at all.
A negated rule excludes any identity that the inner rule matches by mistake. If a group in another app has the same ID as a group you list, the rule quietly excludes that group’s members too. A mistyped ID has the opposite effect, and the rule excludes no identity. Check each ID against GET /v1/groups before you publish.