> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/administration-guide/security/adaptive-authentication/adaptive-authentication-reference.md).

# Adaptive risk types and their values

All fourteen adaptive risk types, the edge each takes when its own evaluation fails, the conformity and value formats, the geolocation properties and the behavioural model.

## What this covers

The exhaustive values behind [Adaptive authentication](/administration-guide/security/adaptive-authentication.md): what each of the fourteen risk types answers, what happens when its dependency is unavailable, what to type into **Adaptive Risk Value** for each, the six geolocation properties, and what the behavioural model actually is. Scan it while you design a graph; the procedure is on the task page.

An adaptive risk is a row in `ADAPTIVE_RISK` — `ADAPTIVE_RISK_ID`, `TYPE_RISK varchar(50)`, `TYPE_CONFORMITY varchar(50)`, `RISK_VALUE varchar(512)`, all present since 4.2.0.0 — referenced from `RULE_AUTHENTICATION_STEP.ADAPTIVE_RISK_ID`. `TYPE_RISK` and `TYPE_CONFORMITY` store the risk type and conformity **names** exactly as they appear in the drop-downs, so a break-glass read of the table lines up directly with what you set on screen.

## The fourteen risk types, and the fail-open flag on each

Each type carries two fixed properties besides its name: whether it needs geolocation, and **what it answers when its own evaluation fails**. The second is the part that decides your graph's behaviour during an outage.

When the signal a risk depends on cannot be reached, OpenIAM does not abandon the flow and does not raise anything to the user. It logs the failure and returns that type's fixed fallback answer. So a type whose fallback is "trusted" **fails open** — the rule takes the `Y` edge exactly as if the check had passed — and a type whose fallback is "not trusted" **fails closed**.

None of this is visible on the risk-node form. What you can observe is the log: the message *"Cannot process risk: %s, params: %s"* in the ESB log means the fallback decided the branch rather than your signal. If you need to know a type's fallback and cannot reproduce the outage, this table is the record — there is no screen that shows it, and only OpenIAM support can attest it for a release other than the one documented here.

| Risk type                      | Geolocation | On failure    | Question it answers                                         |
| ------------------------------ | ----------- | ------------- | ----------------------------------------------------------- |
| `CUSTOM_ADAPTIVE_RISK`         | no          | **fail open** | Whatever your Groovy script decides                         |
| `IS_NEW_IP`                    | no          | fail closed   | Has this user authenticated from this IP before?            |
| `IS_NEW_CITY`                  | **yes**     | **fail open** | Is the resolved city new for this user?                     |
| `MEMBER_OF_COUNTRY`            | **yes**     | fail closed   | Does the IP resolve to one of the configured countries?     |
| `IS_NEW_COUNTRY`               | **yes**     | **fail open** | Is the resolved country new for this user?                  |
| `KNOWN_DEVICE`                 | no          | fail closed   | Is the supplied device identifier recognised?               |
| `USER_AGENT`                   | no          | fail closed   | Does the user agent match the configured value?             |
| `MEMBER_OF_ROLES`              | no          | fail closed   | Does the user hold the configured roles?                    |
| `MEMBER_OF_GROUPS`             | no          | fail closed   | Does the user hold the configured groups?                   |
| `IS_NEW_DEVICE`                | no          | **fail open** | Is the device identifier new?                               |
| `FORGOT_PHONE`                 | no          | fail closed   | Did the mobile flow report the forgot-phone condition?      |
| `IS_RISKY_BEHAVIOURAL_PATTERN` | no          | fail closed   | Does the login resemble the user's learned pattern?         |
| `USER_VERIFIED`                | no          | fail closed   | Has the user completed the verification step?               |
| `HAS_APPROVED_AUTH_TYPE`       | no          | fail closed   | Has an approved factor already been completed in this flow? |

Four types fail open; ten fail closed. **There is no property, field or script that changes this.** Read the table before you design a graph, because "the dependency is down" takes a *different* edge for `IS_NEW_COUNTRY` than for `MEMBER_OF_COUNTRY`, and those two look interchangeable on the form.

## What the `Y` edge means, per type

"Trusted" is the engine's word, not a description of the user. For half of these types a `Y` result is the *risky* condition. Read the middle column as the literal condition that sends the request down the `Y` edge.

| Risk type                      | The `Y` edge fires when                                                                                                        | Declared fallback takes                    |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ |
| `IS_NEW_COUNTRY`               | the resolved country is **new** for this user                                                                                  | `Y`                                        |
| `IS_NEW_CITY`                  | the resolved city is **new** for this user                                                                                     | `Y`                                        |
| `IS_NEW_DEVICE`                | the stored device value is **new** for this user                                                                               | `Y`                                        |
| `IS_NEW_IP`                    | the client IP is **new** for this user                                                                                         | `N`                                        |
| `MEMBER_OF_COUNTRY`            | the resolved country appears in the configured value                                                                           | `N` — as if the user were outside the list |
| `KNOWN_DEVICE`                 | the supplied device identifier is **not** one the user has registered — and also when no device identifier was supplied at all | `N`                                        |
| `USER_AGENT`                   | the configured value contains the request's user agent                                                                         | `N`                                        |
| `MEMBER_OF_ROLES`              | the user's roles match the configured list under the chosen conformity                                                         | `N`                                        |
| `MEMBER_OF_GROUPS`             | the user's groups match the configured list under the chosen conformity                                                        | `N`                                        |
| `HAS_APPROVED_AUTH_TYPE`       | the levels already completed in this flow match the configured list                                                            | `N`                                        |
| `USER_VERIFIED`                | the verification step has been completed                                                                                       | `N`                                        |
| `FORGOT_PHONE`                 | the mobile flow reported the forgot-phone condition                                                                            | `N`                                        |
| `IS_RISKY_BEHAVIOURAL_PATTERN` | establish it empirically — see the polarity warning below                                                                      | `N`                                        |
| `CUSTOM_ADAPTIVE_RISK`         | your script returned `true`                                                                                                    | `Y`                                        |

The right-hand column is the *declared* fallback. For the four `IS_NEW_*` types that fallback is close to unreachable, because those nodes do not perform any lookup: they read an answer computed earlier, during the step that authenticated the identity. When the underlying lookup fails there, nothing is recorded and nothing is compared, and the node reads **"not new"** — the `N` edge. See [What happens when geolocation is unavailable](/administration-guide/security/adaptive-authentication.md#what-happens-when-geolocation-is-unavailable).

### Three consequences to internalise before drawing an edge

**The four `IS_NEW_*` signals are only meaningful after the identity has authenticated.** They are computed once, during the password step, by comparing what arrived against the `USER_AUTH_PARAM` rows for that user, and then carried on the session. A request that has not authenticated yet carries no such record, and every one of the four is treated as **new**. Put an `IS_NEW_*` node above `PASSWORD_AUTH` and it will take the `Y` edge for every request, always.

**A brand-new identity trips every one of them on its first sign-in.** The first authentication has nothing to compare against, so OpenIAM records the IP, the device value and — when geolocation is on — the city and country, and reports all of them as new.

**What `IS_NEW_DEVICE` compares is the browser's user agent.** On a web sign-in the device value OpenIAM stores and compares is the `User-Agent` header, or the literal `NO INFORMATION` when the header is missing. It is not a device fingerprint and it is not a cookie, so a browser version upgrade trips it. `KNOWN_DEVICE` is a different mechanism entirely: it compares a device identifier supplied in a request header against devices registered to the user, which is a mobile-application signal, not a browser one.

### Known issue: `IS_RISKY_BEHAVIOURAL_PATTERN` is logged as `FORGOT_PHONE`

`IS_RISKY_BEHAVIOURAL_PATTERN` shares an internal label with `FORGOT_PHONE`, and one place in the product prints that label rather than the type's own name. The consequence you will meet is in the log: the *"%s type of adaptive risk not supported"* message reads `FORGOT_PHONE` when the risk that failed was in fact the behavioural-pattern one. If you see that message naming `FORGOT_PHONE` on a rule that has no `FORGOT_PHONE` node in it, the message is about your behavioural node.

**What is not affected:** the stored value and the actual dispatch. `ADAPTIVE_RISK.TYPE_RISK` holds `IS_RISKY_BEHAVIOURAL_PATTERN`, correctly, and the rule evaluates the behavioural risk, correctly. Report it as a product defect rather than treating it as a misconfiguration on your side.

## Conformity, and the three types that read it

`TYPE_CONFORMITY` offers exactly four values, and no more can be added:

| Value        | Meaning against the configured `RISK_VALUE` list |
| ------------ | ------------------------------------------------ |
| `EQUALS`     | All configured values must be held               |
| `IN`         | Any one configured value is enough               |
| `NOT_EQUALS` | Negation of `EQUALS`                             |
| `NOT_IN`     | None of the configured values may be held        |

**Conformity applies to three types only.** `MEMBER_OF_ROLES`, `MEMBER_OF_GROUPS` and `HAS_APPROVED_AUTH_TYPE` are the only types that read it; every other type ignores whatever is stored. You do not have to take that on trust — select a risk node and look at the **Select conformity type** drop-down: it is greyed out for every type that ignores it. In particular `MEMBER_OF_COUNTRY` ignores conformity entirely, so setting it to `NOT_IN` in the hope of an "anywhere except these countries" rule does nothing; invert the branch instead by swapping which edge carries `Y`.

## What to type into Adaptive Risk Value

`RISK_VALUE` is 512 characters, labelled **Adaptive Risk Value**. The format is not the same for every type, and getting it wrong fails silently:

| Type                                                                                        | What to put in **Adaptive Risk Value**                    | Getting it wrong                                                                                                                                                                                                                                                                                |
| ------------------------------------------------------------------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MEMBER_OF_ROLES`, `MEMBER_OF_GROUPS`                                                       | A **JSON array of IDs** — `["ROLE_ID_ONE","ROLE_ID_TWO"]` | A comma-separated list is not parsed as a list. It is taken as a single ID, which matches nothing                                                                                                                                                                                               |
| `HAS_APPROVED_AUTH_TYPE`                                                                    | A JSON array of level names — `["TOTP_AUTH"]`             | An unparseable name is dropped and logged as *"Can't parse authentication type"*                                                                                                                                                                                                                |
| `MEMBER_OF_COUNTRY`                                                                         | Two-letter country codes with a separator — `US,CA,GB`    | The stored value is searched for the resolved code as a **substring**, so run the codes together as `USCAGB` and `SC` matches too. Always separate them                                                                                                                                         |
| `USER_AGENT`                                                                                | The complete user-agent strings you accept, concatenated  | The test is whether your stored value **contains** the request's user agent, not the other way round. A fragment such as `Chrome` matches nothing; 512 characters holds two or three full strings at most, which makes this type impractical for anything but a fixed kiosk or a service client |
| `CUSTOM_ADAPTIVE_RISK`                                                                      | The Groovy script ID, from the picker                     | Blank, wrong, or a missing script all return `true`                                                                                                                                                                                                                                             |
| `IS_NEW_*`, `KNOWN_DEVICE`, `FORGOT_PHONE`, `USER_VERIFIED`, `IS_RISKY_BEHAVIOURAL_PATTERN` | Nothing. The value field is not offered                   | —                                                                                                                                                                                                                                                                                               |

Use object IDs, never display names: a role's display name in `RISK_VALUE` simply never matches, silently. A single ID with no brackets does work for the membership types, because an unparseable value is trimmed and used as one entry — which is why `["ROLE_ONE"]` and `ROLE_ONE` both behave and `ROLE_ONE,ROLE_TWO` does not. Country codes are the two-letter form, because that is what the shipped geolocation lookup returns and what OpenIAM stores in `USER_AUTH_PARAM`; country *names* never match.

Every IP-derived risk fails in one of three ways, and each is a plain log line rather than an error to the user: *"User ID not set. "* and *"User IP not set. "* when the request arrived without the identity or address the risk needs, and *"Can't get Geo location information by IP"* when a geolocation type gets nothing back for the address. Those three are precisely the failures the fail-open or fail-closed fallback then decides.

## Geolocation

Six properties, category `IP`, with their final shipped values:

| Property                                     | Shipped                                                      | Type    | Label                                 |
| -------------------------------------------- | ------------------------------------------------------------ | ------- | ------------------------------------- |
| `org.openiam.adaptive.risk.geo.location.use` | `false`                                                      | Boolean | Activate Geolocation in Adaptive Risk |
| `org.openiam.ip.geo.use.custom.api`          | `true`                                                       | Boolean | Use Custom IP Geolocation API         |
| `org.openiam.ip.geo.custom.api.groovy`       | `geo/IPGeoAPIDefaultScript.groovy`                           | Groovy  | Custom IP Location API Script         |
| `org.openiam.ip.geo.first.search.in.db`      | `false`                                                      | Boolean | Search IP Location in Local DB        |
| `org.openiam.ip.geo.save.new.in.db`          | `true`                                                       | Boolean | Save New IP Location in Local DB      |
| `org.openiam.ip.geo.db.for.upload`           | `/data/openiam/conf/iamscripts/geo/db/dbip-city-lite.csv.gz` | String  | Geo IP Database for Upload            |

All six have shipped since 4.2.0.0, and the script path was last changed in 4.2.2.0. Read the current values in the `IP` category of **Administration → System Configuration**.

Read those defaults as a shipped posture: **the master switch is off, but the moment you turn it on the default is to call an external API first** (`use.custom.api` `true`, `first.search.in.db` `false`) and cache the answer locally (`save.new.in.db` `true`). If your deployment cannot make outbound calls, set `org.openiam.ip.geo.first.search.in.db` to `true` and load the local database from the path in `org.openiam.ip.geo.db.for.upload`. The shipped script is `geo/IPGeoAPIDefaultScript.groovy`, editable in the Groovy editor; the shipped database file is a DB-IP city-lite CSV, which resolves city and country only — never a street address, and never an ASN.

You can tell whether geolocation is resolving without reading any code: turn the master switch on, sign in as a test user, and look for `city` and `country` on that user's most recent `urifederationstatistic` document.

The six `org.openiam.approve.new.*` properties in the `Authentication` category are the *notification* half of the same signals and all ship `false`; see [Authentication levels, rules and capability flags](/administration-guide/security/authentication-providers-and-mfa/authentication-provider-reference.md#the-authentication-property-category).

## Behavioural risk: what the model actually is

`IS_RISKY_BEHAVIOURAL_PATTERN` is the only signal OpenIAM does not evaluate itself. It asks a separate machine-learning service, over the `BehaviouralPatternQueue` message queue, and uses the answer that comes back. If that service is not deployed, the risk fails **closed**.

### The statistics index

Training data is the OpenSearch index **`urifederationstatistic`**, and OpenIAM writes a document to it on every authentication-bearing metadata call — that is, every one whose resolved authentication levels are not just `NONE`. The document has nine fields: `userId`, `ipAddress`, `time`, `deviceId`, `country`, `city`, `userAgent`, `entitledToHighRiskRBACObject` and `closeToCookieCreationDate`.

Two things follow that you can check. `entitledToHighRiskRBACObject` is derived from whether the user is entitled to a resource classified as high risk, which is how resource risk classification feeds authentication risk — so a change to resource classification changes future behavioural training data. And `city` and `country` are populated only when geolocation resolves, so with the geolocation master switch off, two of the model's three highest-weighted features are permanently empty.

**OpenIAM does not create this index.** It is created, with its templates, by the deployment's index-management container so that it can be rolled over. If the index is absent, statistics are simply not written and no console screen reports it — confirm the index exists with your deployment owner, or see the [Installation Guide](/installation/installation.md).

### The three numbers that change how you design a rule

The model is a per-user outlier detector, retrained from that user's recent history, and none of its parameters is configurable from OpenIAM — they belong to the machine-learning service, and changing them is a deployment task covered by the [Installation Guide](/installation/installation.md).

* **A user needs 1,000 statistics documents before a real model exists.** Below that, the service returns a fixed answer from a stub rather than a prediction. New and low-activity users are therefore not being assessed at all. Never make behavioural risk the only control for a population that does not yet have a thousand sign-ins each. Count the documents for a `userId` to see where a user stands.
* **Country and device dominate the score**, by two to three orders of magnitude over IP and user agent, with city between them. Time of day contributes as hour-and-minute only — **there is no day-of-week feature**, so a Sunday sign-in at a normal hour looks ordinary to the model.
* **A missing field and an unseen value are different inputs.** "No device ID was supplied" scores differently from "a device ID never seen before". That is why an empty `deviceId` on the statistics documents undermines the signal rather than strengthening it.

Results are cached, in the service's own memory for three minutes and in Redis per user. Sending `CLEAR_CACHE` on the same queue drops both for one user — that is how you force a retrain after correcting a user's history. Publishing to that queue is not something the Webconsole does; it is a deployment-side operation. None of these values can be read or confirmed from the Webconsole; your deployment owner is the one who can confirm the version you are running.

{% hint style="warning" %}
**Establish the polarity empirically before you connect either edge.** The value the model produces means "this login resembles the learned pattern", and by the time it reaches the rule it has been renamed once and negated once. Those two do not obviously cancel, and following the naming literally gives you a *normal* login taking the `N` edge — the same edge the cold-start stub produces for a user with no model.

Do not guess, and do not take this page's word for it either: it is a two-minute experiment and no screen states the answer. Build a rule whose `Y` and `N` edges lead to two visibly different outcomes, sign in as a user with a mature model — a thousand statistics documents or more — and observe which edge fires. Then wire the real rule from what you observed, and record the result for your release. Note also that an **anonymous** request is treated as trusted without the model being consulted at all, so test with an identified user.
{% endhint %}

## Related

* [Adaptive authentication](/administration-guide/security/adaptive-authentication.md) — the worked rule this page supports
* [Search indexes and the authorization graph](/administration-guide/reference/search-indexes.md) — `urifederationstatistic` among the 32 indexes
* [Enumerated value reference](/administration-guide/reference/enums.md) — the adaptive risk types and conformity types as declared
* [System property reference](/administration-guide/reference/system-properties.md) — the `IP` category

*Applies to OpenIAM 2026.8.1. Last reviewed 7 August 2026.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/administration-guide/security/adaptive-authentication/adaptive-authentication-reference.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
