---
title: Script bindings
description: Access HTTP services, configure logging, retrieve script names, and reference environment secrets and variables in PingAM scripts using common script bindings
component: pingam
version: 7.5
page_id: pingam:am-scripting:script-bindings
canonical_url: https://docs.pingidentity.com/pingam/8.1/am-scripting/script-bindings.html
llms_txt: https://docs.pingidentity.com/pingam/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
keywords: ["Scripts", "ESVs"]
page_aliases: ["scripting-guide:script-bindings.adoc"]
superseded_by: https://docs.pingidentity.com/pingam/8.1/am-scripting/script-bindings.html
section_ids:
  common-bindings: Common bindings
  httpclient: Access HTTP services
  methods: Methods
  httpclient-sync-get: "Example: Send a synchronous request (GET)"
  httpclient-sync: "Example: Send a synchronous request (POST)"
  httpclient-async: "Example: Send an asynchronous request"
  logger: Log script messages
  scriptName: Output script name
  common-utils: Access utility functions
  systemenv: Reference substituted properties in scripts
---

# Script bindings

Each [script type](scripting-api.html) exposes a number of *bindings*, objects that PingAM injects into the script execution context. The bindings provide a stable way of accessing PingAM functionality, without the need to allowlist Java classes. Scripts are provided with all the bindings for their context at the point of execution.

Find information about context-specific bindings in the documentation for each script type.

|   |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| - | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | PingAM has introduced a [next-generation scripting engine](next-generation-scripts.html) that offers several benefits, including enhanced script bindings.For decision node scripts, the availability and usage of bindings depend on the script engine version of the script: legacy or next-generation. Both versions are described in this section.The next-generation script engine is *only* available to the [Scripted decision node API](scripting-api-node.html).For information about migrating to the enhanced scripting engine, refer to [Migrating to next-generation scripts](next-generation-scripts.html#migrate-to-v2-steps). |

## Common bindings

The following bindings are common to many authentication and authorization scripts. Use these bindings to access data and perform script operations such as logging.

| Binding      | Description                                                    | Further information                                       |
| ------------ | -------------------------------------------------------------- | --------------------------------------------------------- |
| `httpClient` | Make outbound HTTP calls.                                      | [Access HTTP services](#httpclient)                       |
| `logger`     | Write a message to the PingAM debug log.                       | [Log script messages](#logger)                            |
| `scriptName` | Access the name of the running script.                         | [Output script name](#scriptName)                         |
| `systemEnv`  | Reference environment secrets and variables (ESVs) in scripts. | [Reference substituted properties in scripts](#systemenv) |

### Access HTTP services

Call HTTP services with the `httpClient.send` method. HTTP client requests are asynchronous, unless you invoke the `get()` method on the returned object.

### Methods

* Next-generation

* Legacy

The `httpClient` binding uses native JavaScript objects to send requests and receive responses, in a similar way to the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).

* To invoke an HTTP request

  * **`ResponseScriptWrapper httpClient.send(String uri, Map requestOptions).get()`**

    Sends a synchronous request to the specified URI with request options.

    The `requestOptions` parameter is a native JavaScript object that supports these attributes:

    | Field     | Type                                                                                                                                                                                                                                                                                                          |
    | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `method`  | The HTTP request method. For example, `GET`, `POST`, `PUT`, `DELETE`, `HEAD`.                                                                                                                                                                                                                                 |
    | `headers` | The request headers. For example, `"Content-Type": "application/json"`.&#xA;&#xA;For requests that require headers to be application/x-www-form-urlencoded, encode the body content. For example:&#xA;&#xA;{&#xA;  body: encodeURI("grant_type=client_credentials\&client_id=ID\&client_secret=SECRET")&#xA;} |
    | `token`   | The token specified as the Authorization header, for example, when [sending a synchronous request](#httpclient-sync).                                                                                                                                                                                         |
    | `body`    | The content of the request. Not specified for `GET`, `HEAD`, or `TRACE` methods.                                                                                                                                                                                                                              |

  * **`ResponseScriptWrapper httpClient.send(String uri).get()`**

    Sends a synchronous GET request with no additional request options.

* To access response data

  * **`Map response.formData()`**

  * **`Map response.json()`**

  * **`String response.text()`**

    The following fields provide response status information:

    | Field        | Type    |
    | ------------ | ------- |
    | `headers`    | Map     |
    | `ok`         | boolean |
    | `status`     | integer |
    | `statusText` | String  |

    The response is similar to [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object behavior.

To invoke a synchronous HTTP request:

* **`HTTPClientResponse httpClient.send(Request request).get()`**

  To access response data:

* **`JSON.parse(response.getEntity().getString())`**

`HttpClientResponse` methods:

* **`Map<String, String> getCookies()`**

* **`String getEntity`**

* **`Map<String, String> getHeaders()`**

* **`String getReasonPhrase()`**

* **`Integer getStatusCode()`**

* **`Boolean hasCookies`**

* **`Boolean hasHeaders`**

The following examples demonstrate different ways to send HTTP client requests:

* [Example: Send a synchronous request (GET)](#httpclient-sync-get)

* [Example: Send a synchronous request (POST)](#httpclient-sync)

* [Example: Send an asynchronous request](#httpclient-async)

|   |                                                                                                                                                                     |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | For an example of how to use `httpClient` with basic authorization, refer to [Access credentials and secrets](scripting-api-node.html#scripting-api-authn-secrets). |

### Example: Send a synchronous request (GET)

The following example uses the `httpClient` binding in a next-generation script to make a GET request to generate random UUIDs.

```javascript
var BASE_URL = "http://www.randomnumberapi.com/api/v1.0/uuid";

var COUNT = 5;

var options = {
  method: "GET",
  headers: {
    "Content-Type": "application/json; charset=UTF-8"
  }
};
var requestURL = `${BASE_URL}?count=${COUNT}`;

var response = httpClient.send(requestURL, options).get();

if (response.status === 200) {
  var uuids = JSON.parse(response.text());
  nodeState.putShared("UUIDs", uuids);
  action.goTo("true");
} else {
  logger.error("Error generating UUIDs: " + response.statusText);
  action.goTo("false");
}
```

Use a [Debug node](https://docs.pingidentity.com/auth-node-ref/7.5/am-only/debug.html) to verify the UUIDs stored in `nodeState`. For example:

```json
{
    ...
    "UUIDs": [
        "d787a51e-7b2a-4eba-9d87-7ec555ec9f32",
        "f561381a-ec03-4b48-8d6d-828c80d43805",
        "6d9ef759-be3d-414d-a942-dce8d3840b59",
        "40737769-0c91-41e9-a0c4-7deab4a15aea",
        "5a40bdb6-62d3-406f-bd23-71be1d5a54f5"
    ]
}
```

### Example: Send a synchronous request (POST)

The following example uses the `httpClient` binding to send a synchronous authentication request and check for success.

* Next-generation

* Legacy

```javascript
var bearerToken = "Bearer abcd-1234";

var requestOptions = {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  token: bearerToken, // Equivalent to Authorization header
  body: {
    username: "demo"
  }
}

var requestURL = "https://my.auth.server/authenticate";
var response = httpClient.send(requestURL, requestOptions).get();

if (response.status === 200) {
    action.goTo("true");
} else {
    action.goTo("false");
}
```

```javascript
var fr = JavaImporter(org.forgerock.openam.auth.node.api.Action);

var requestURL = "https://my.auth.server/authenticate";
var request = new org.forgerock.http.protocol.Request();
request.setUri(requestURL);
request.setMethod("POST");
request.getHeaders().add("Content-Type", "application/json;");
request.getHeaders().add("Authorization", "Bearer abcd-1234");
request.setEntity(JSON.stringify({"username": "demo"}));

var response = httpClient.send(request).get();

var responseCode = response.getStatus().getCode();
if (responseCode === 200) {
    action = fr.Action.goTo("true").build();
} else {
    action = fr.Action.goTo("false").build();
}
```

### Example: Send an asynchronous request

The `httpclient` binding also supports asynchronous requests so that you can perform non-blocking operations, such as recording logging output after the script has completed.

To make an asynchronous request, use the same method signatures to send the request but without calling `get()` on the returned object. The `send()` method then initiates a separate thread to handle the response. Callers are unable to control when the asynchronous call is processed, so won't be able to use the response as part of authentication processing.

* Next-generation

* Legacy

```javascript
public Promise<ResponseScriptWrapper, HttpClientScriptException> send(String uri)
public Promise<ResponseScriptWrapper, HttpClientScriptException> send(String uri, Map<String, Object> requestOptions)
```

```javascript
public Promise<Response, NeverThrowsException> send(Request request)
```

For example:

* Next-generation

* Legacy

```javascript
var requestURL = "https://my.auth.server/audit";
// creates separate thread to handle response
var response = httpClient.send(requestURL).then((response) => {
  if (!response) {
    logger.error("Bad response from " + requestURL);
    return;
  }
  if (response.status != 200) {
    logger.error("Unexpected response: " + response.statusText);
    return;
  }
  logger.debug("Returned from async request");
});
// continues processing whilst awaiting response
action.goTo("true");
```

```javascript
var fr = JavaImporter(
    org.forgerock.http.protocol.Request,
    org.forgerock.http.protocol.Response,
    org.forgerock.openam.auth.node.api.Action);

var request = new fr.Request();
request.setUri("https://my.auth.server/audit");
request.setMethod("GET");

var response = httpClient.send(request).then((response) => {
  if (!response) {
    logger.error("Bad response from " + requestURL);
    return;
  }
  var status = response.getStatus().getCode();

  if (status != 200) {
    logger.error("Unexpected response: " + response.getEntity().getString());
    return;
  }
  logger.message("Returned from async request");
});

action = fr.Action.goTo("true").build();
```

### Log script messages

Write messages to PingAM debug logs by using the `logger` object.

Scripts that create debug messages have their own logger which is created after the script has executed at least once.

Logger names use the format: ``scripts.<context>.<script UUID>.(<script name>); for example, `scripts.OIDC_CLAIMS.36863ffb-40ec-48b9-94b1-9a99f71cc3b5.(OIDC Claims Script)``.

For information about debug logs, refer to [Debug logging](../maintenance/debug-logging.html).

* Next-generation

* Legacy

The `ScriptedLoggerWrapper` is based on the [SLF4J](https://www.slf4j.org) logging framework. You can log messages at the following levels:

* Trace

* Debug

* Info

* Warn

* Error

```javascript
var traceEnabled = logger.isTraceEnabled();
logger.trace("Trace with arg {}", arg);
var debugEnabled = logger.isDebugEnabled();
logger.debug("Debug with arg {}", arg);
var infoEnabled = logger.isInfoEnabled();
logger.info("Info with arg {}", arg);
var warnEnabled = logger.isWarnEnabled();
logger.warn("Warn with arg {}", arg);
var errorEnabled = logger.isErrorEnabled();
logger.error("Error with arg {}", arg);
```

The `Debug` logger lets you log messages at the following levels:

* Message

* Warning

* Error

```javascript
var messageEnabled = logger.messageEnabled();
logger.message("Message with arg {}", arg);
var warnEnabled = logger.warningEnabled();
logger.warning("Warn with arg {}", arg);
var errorEnabled = logger.errorEnabled();
logger.error("Error with arg {}", arg);
```

### Output script name

Use the `scriptName` binding to get the name of the running script as a string.

* Next-generation

* Legacy

```javascript
// log current script name
logger.debug("Running script: " + scriptName);

// or use a library script to log script name
var mylib = require('loggingLibrary');
mylib.debug(logger, scriptName);
```

```javascript
// log current script name
logger.message("Running script: " + scriptName);
```

### Access utility functions

Use the `utils` binding to base64 encode or decode text and generate random UUIDs.

* Next-generation

* Legacy

```javascript
// generate a pseudorandom UUID (version 4)
var uuid = utils.crypto.randomUUID();
logger.debug("UUID: " + uuid); //eef5b4e1-ae86-4c0a-9160-5afee2b5e791

// encode a string
var encoded = utils.base64.encode("exampletext")
logger.debug("Encoded text: " + encoded); //ZXhhbXBsZXRleHQ=

var decoded = utils.base64.decode(encoded);
logger.debug("Decoded text: " + decoded);

// encode a URL
var encodedURL = utils.base64url.encode("http://exampletext=")
logger.debug("Encoded URL: " + encodedURL); //aHR0cDovL2V4YW1wbGV0ZXh0PQ

var decodedURL = utils.base64url.decode(encodedURL);
logger.debug("Decoded URL: " + decodedURL);
```

*Not available in Legacy bindings*

### Reference substituted properties in scripts

The `systemEnv` binding, available to all script types, includes an instance of the [ScriptPropertyResolver](../_attachments/apidocs/org/forgerock/openam/scripting/api/ScriptPropertyResolver.html) class. This interface exposes the following methods:

```java
String getProperty(String propertyName);
String getProperty(String propertyName, String defaultValue);
<T> T getProperty(String propertyName, String defaultValue, Class<T> returnType);
```

where:

* `propertyName` refers to a [configuration expression](../setup/property-value-substitution.html#configuration-expressions) (without the ampersand or braces)

  To reference a substituted property in a script, the property name *must* include a specific prefix; for example, `script.my.variable`. This prefix decreases the risk that random property values are resolved in scripts.

  The default prefix for all script types, is `script`. To change the prefix, go to Configure > Global Services > Scripting > Secondary Configurations > *Script Type* > Secondary Configurations > Engine Configuration > Property Name Prefix.

  Make sure `propertyName` is specific enough to distinguish it from other configuration expressions.

* `defaultValue` is the default value set for that property in the configuration expression

  The `defaultValue` must not be `null`.

* `returnType` is one of the following fully-qualified Java class names:

  * `java.lang.Boolean`

  * `java.lang.Double`

  * `java.lang.Integer`

  * `java.lang.String`

  * `java.util.List`

  * `java.util.Map`

The `getProperty(String propertyName)` method returns `null` when the `propertyName` is not valid.

For example:

The following example assumes that the property name prefix is set to `script` in the script engine configuration for the script type.

The script is used in a [Scripted Decision node](https://docs.pingidentity.com/auth-node-ref/7.5/scripted-decision.html) to get the values such as the user's `email`, `hostname`, and `port`. The script also shows type transformation where the type isn't a string.

* JavaScript

* Groovy

```javascript
var fr = JavaImporter(org.forgerock.openam.auth.node.api.Action);

// Properties should get resolved (set in AM)
var email = systemEnv.getProperty('script.tree.decision.node.email');
var name = systemEnv.getProperty('script.tree.decision.node.hostname', 'defaultHostname');
var port = systemEnv.getProperty('script.tree.decision.node.port', '587', java.lang.Integer);
var double = systemEnv.getProperty('script.tree.decision.node.double', '2.0', java.lang.Double);
var hasPort = systemEnv.getProperty('script.tree.decision.node.hasPort', 'false', java.lang.Boolean);
var map = systemEnv.getProperty('script.tree.decision.node.map', '{"defaultKey":"defaultValue"}', java.util.Map);
var list = systemEnv.getProperty('script.tree.decision.node.list', 'defaultValue', java.util.List);

// Properties should get resolved to their defaults (not set in AM)
var defaultName = systemEnv.getProperty('script.tree.decision.node.hostname.unresolved', 'defaultHostname');
var defaultPort = systemEnv.getProperty('script.tree.decision.node.port.unresolved', '587', java.lang.Integer);
var defaultDouble = systemEnv.getProperty('script.tree.decision.node.double.unresolved', '2.0', java.lang.Double);
var defaultHasPort = systemEnv.getProperty('script.tree.decision.node.hasPort.unresolved', 'false', java.lang.Boolean);
var defaultMap = systemEnv.getProperty('script.tree.decision.node.map.unresolved', '{"defaultKey":"defaultValue"}', java.util.Map);
var defaultList = systemEnv.getProperty('script.tree.decision.node.list.unresolved', 'defaultFirstValue,defaultSecondValue', java.util.List);

// Assert all property values - set the appropriate outcome
if (email === 'test@example.com' && name === 'testHostname' && port === 25 && double === 1.0 && hasPort === true
    && map.get('testKey') == 'testValue' && list == '[testFirstValue, testSecondValue]'
    && defaultName === 'defaultHostname' && defaultPort === 587 && defaultDouble === 2.0 && defaultHasPort === false
    && defaultMap.get('defaultKey') == 'defaultValue' && defaultList == '[defaultFirstValue, defaultSecondValue]') {
  action = fr.Action.goTo("true").build();
} else {
  action = fr.Action.goTo("false").build();
}
```

```groovy
// Properties should get resolved (set in AM)
String email = systemEnv.getProperty('script.tree.decision.node.email');
String name = systemEnv.getProperty('script.tree.decision.node.hostname', 'defaultHostname');
Integer port = systemEnv.getProperty('script.tree.decision.node.port', '587', java.lang.Integer);
Double testDouble = systemEnv.getProperty('script.tree.decision.node.double', '2.0', java.lang.Double);
Boolean hasPort = systemEnv.getProperty('script.tree.decision.node.hasPort', 'false', java.lang.Boolean);
Map map = systemEnv.getProperty('script.tree.decision.node.map', '{\"defaultKey\":\"defaultValue\"}', java.util.Map);
List list = systemEnv.getProperty('script.tree.decision.node.list', 'defaultValue', java.util.List);

// Properties should get resolved to their defaults (not set in AM)
String defaultName = systemEnv.getProperty('script.tree.decision.node.hostname.unresolved', 'defaultHostname');
Integer defaultPort = systemEnv.getProperty('script.tree.decision.node.port.unresolved', '587', java.lang.Integer);
Double defaultDouble = systemEnv.getProperty('script.tree.decision.node.double.unresolved', '2.0', java.lang.Double);
Boolean defaultHasPort = systemEnv.getProperty('script.tree.decision.node.hasPort.unresolved', 'false', java.lang.Boolean);
Map defaultMap = systemEnv.getProperty('script.tree.decision.node.map.unresolved', '{\"defaultKey\":\"defaultValue\"}', java.util.Map);
List defaultList = systemEnv.getProperty('script.tree.decision.node.list.unresolved', 'defaultFirstValue,defaultSecondValue', java.util.List);

// Assert all property values - set the appropriate outcome
if (email.equals('test@example.com') && name.equals('testHostname') && port == 25 && testDouble == 1.0d && hasPort == true
    && defaultName.equals('defaultHostname') && defaultPort == 587 && defaultDouble == 2.0d && defaultHasPort == false
    && map.get('testKey').equals('testValue')
    && list.get(0).equals('testFirstValue') && list.get(1).equals('testSecondValue')
    && defaultMap.get('defaultKey').equals('defaultValue')
    && defaultList.get(0).equals('defaultFirstValue') && defaultList.get(1).equals('defaultSecondValue')) {

    outcome = 'true';
} else {
    outcome = 'false';
}
```
