---
title: Create custom endpoints to launch scripts
description: Custom endpoints let you run arbitrary scripts through the REST API.
component: pingidm
version: 8.1
page_id: pingidm:scripting-guide:script-custom-endpoints
canonical_url: https://docs.pingidentity.com/pingidm/8.1/scripting-guide/script-custom-endpoints.html
keywords: ["Scripting", "REST", "Endpoints", "Custom"]
section_ids:
  adding-custom-endpoints-structure: Custom endpoint configuration
  custom-endpoint-scripts: Custom endpoint scripts
  custom-script-errors: Script exceptions
  custom-endpoint-api-descriptor: Write an API descriptor for a custom endpoint
---

# Create custom endpoints to launch scripts

*Custom endpoints* let you run arbitrary scripts through the REST API.

A custom endpoint configuration *(tooltip: You can create and change custom endpoint configurations over REST at the config/endpoint/\<name> endpoint, or in files named conf/endpoint-\<name>.json, where \<name> generally describes the purpose of the endpoint.)* includes an inline script or script file reference in JavaScript or Groovy. The script provides the endpoint functionality.

> **Collapse: Sample Custom Endpoint Configuration**
>
> ```json
> {
>     "type" : "text/javascript",
>     "source" : "<script>",
>     "apiDescription" : {
>         "title" : "Echo",
>         "description" : "Service that echo's your HTTP requests.",
>         "mvccSupported" : false,
>         "create" : {
>             "description" : "Echo a CREATE request.",
>             "mode" : "ID_FROM_SERVER",
>             "singleton" : false
>         },
>         "read" : { "description" : "Echo a READ request." },
>         "update" : { "description" : "Echo an UPDATE request." },
>         "delete" : { "description" : "Echo a DELETE request." },
>         "patch" : {
>             "description" : "Echo a PATCH request.",
>             "operations" : [ "ADD", "REMOVE", "REPLACE", "INCREMENT", "COPY", "MOVE", "TRANSFORM" ]
>         },
>         "actions" : [
>             {
>                 "description" : "Echo an ACTION request.",
>                 "name" : "echo",
>                 "request" : { "type" : "object" },
>                 "response" : {
>                     "title" : "Echo action response",
>                     "type" : "object",
>                     "properties" : {
>                         "method" : {
>                             "type" : "string",
>                             "enum" : [ "action" ]
>                         },
>                         "action" : { "type" : "string" },
>                         "content" : { "type" : "object" },
>                         "parameters" : { "type" : "object" },
>                         "context" : { "type" : "object" }
>                     }
>                 }
>             }
>         ],
>         "queries" : [
>             {
>                 "description" : "Echo a query-filter request.",
>                 "type" : "FILTER",
>                 "queryableFields" : [ "*" ]
>             },
>             {
>                 "description" : "Echo a query-all request.",
>                 "type" : "ID",
>                 "queryId" : "query-all"
>             },
>             {
>                 "description" : "Echo a query-all-ids request.",
>                 "type" : "ID",
>                 "queryId" : "query-all-ids"
>             }
>         ],
>         "resourceSchema" : {
>             "title" : "Echo resource",
>             "type" : "object",
>             "properties" : {
>                 "method" : {
>                     "title" : "CREST method",
>                     "type" : "string"
>                 },
>                 "resourceName" : { "type" : "string" },
>                 "parameters" : { "type" : "object" },
>                 "context" : { "type" : "object" }
>             }
>         }
>     }
> }
> ```

> **Collapse: Sample Custom Endpoint Script**
>
> ```javascript
> (function(){
>     if (request.method === "create") {
>         return {
>             method: "create",
>             resourceName: request.resourcePath,
>             newResourceId: request.newResourceId,
>             parameters: request.additionalParameters,
>             content: request.content,
>             context: context.current
>         };
>     } else if (request.method === "read") {
>         return {
>             method: "read",
>             resourceName: request.resourcePath,
>             parameters: request.additionalParameters,
>             context: context.current
>         };
>     } else if (request.method === "update") {
>         return {
>             method: "update",
>             resourceName: request.resourcePath,
>             revision: request.revision,
>             parameters: request.additionalParameters,
>             content: request.content,
>             context: context.current
>         };
>     } else if (request.method === "patch") {
>         return {
>             method: "patch",
>             resourceName: request.resourcePath,
>             revision: request.revision,
>             parameters: request.additionalParameters,
>             patch: request.patchOperations,
>             context: context.current
>         };
>     } else if (request.method === "query") {
>         // query results must be returned as a list of maps
>         return [ {
>             method: "query",
>             resourceName: request.resourcePath,
>             pagedResultsCookie: request.pagedResultsCookie,
>             pagedResultsOffset: request.pagedResultsOffset,
>             pageSize: request.pageSize,
>             queryId: request.queryId,
>             queryFilter: request.queryFilter.toString(),
>             parameters: request.additionalParameters,
>             content: request.content,
>             context: context.current
>         } ];
>     } else if (request.method === "delete") {
>         return {
>             method: "delete",
>             resourceName: request.resourcePath,
>             revision: request.revision,
>             parameters: request.additionalParameters,
>             context: context.current
>         };
>     } else if (request.method === "action") {
>         return {
>             method: "action",
>             action: request.action,
>             content: request.content,
>             parameters: request.additionalParameters,
>             context: context.current
>         };
>     } else {
>         throw { code : 500, message : "Unknown request type " + request.method };
>     }
> })();
> ```

A sample custom endpoint configuration is provided in the `openidm/samples/example-configurations/custom-endpoint` directory. The sample includes three files:

* conf/endpoint-echo.json

  Provides the configuration for the endpoint.

* script/echo.js

  Provides the endpoint functionality in JavaScript.

* script/echo.groovy

  Provides the endpoint functionality in Groovy.

|   |                                                                                                                               |
| - | ----------------------------------------------------------------------------------------------------------------------------- |
|   | This sample endpoint is described in detail in the sample: [Create a custom endpoint](../samples-guide/custom-endpoint.html). |

## Custom endpoint configuration

A custom endpoint configuration *(tooltip: You can create and change custom endpoint configurations over REST at the config/endpoint/\<name> endpoint, or in files named conf/endpoint-\<name>.json, where \<name> generally describes the purpose of the endpoint.)* has the following structure:

```json
{
    "context" : "context path",
    "type" : "script language",
    "source" : "script source" | "file" : "script file",
    "apiDescription" : "API descriptor object"
}
```

* `context`

  string, optional

  The root URL path for the endpoint, in other words, the *route* to the endpoint. An endpoint with the context `endpoint/test` is addressable over REST at the URL `http://localhost:8080/openidm/endpoint/test` or by using a script such as `openidm.read("endpoint/test")`.

  Endpoint contexts support wild cards, as shown in the preceding example. The `endpoint/linkedview/*` route matches the following patterns:

  ```
  endpoint/linkedView/managed/user/bjensen
  endpoint/linkedView/system/ldap/account/bjensen
  endpoint/linkedView/
  endpoint/linkedView
  ```

  The `context` parameter is not mandatory in the endpoint configuration file. If you do not include a `context`, the route to the endpoint is identified by the name of the file. For example, in the sample endpoint configuration provided in `openidm/samples/example-configurations/custom-endpoint/conf/endpoint-echo.json`, the route to the endpoint is `endpoint/echo`.

* `type`

  string, required

  The script type.

  IDM supports `"text/javascript"` and `"groovy"`.

* `file` or `source`

  The path to the script file, or the script itself, inline.

  For example:

  ```none
  "file" : "workflow/gettasksview.js"
  ```

  or

  ```none
  "source" : "require('linkedView').fetch(request.resourcePath);"
  ```

* `apiDescription`

  JSON object, optional

  Describes the custom endpoint and includes its documentation in the [REST API Explorer](../rest-api-reference/api-explorer.html).

|   |                                                                                                                                                                                                                                                                                                        |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|   | Custom endpoints do not support versioning.You must set authorization for any custom endpoints that you add, for example, by restricting the methods to the appropriate roles. For more information, refer to [Authorization and Roles](../auth-guide/authorization-and-roles.html#idm-authorization). |

## Custom endpoint scripts

The custom endpoint script files in the `samples/example-configurations/custom-endpoint/script` directory demonstrate all the HTTP operations that can be called by a script.

Each HTTP operation is associated with a `method`. Allowed methods are:

* `create`

* `read`

* `update`

* `delete`

* `patch`

* `action`

* `query`

Requests sent to the custom endpoint return a list of the variables available to each method.

All scripts are invoked with a global `request` variable in their scope. This request structure carries all the information about the request.

|   |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| - | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | `Read` requests on custom endpoints must not modify the state of the resource, either on the client or the server, as this can make them susceptible to Cross-Site Request Forgery (CSRF) exploits.Endpoints which only read data without modifying state are inherently safe from CSRF attacks. This is consistent with the US National Security Agency's *Guidelines for Implementation of REST*, which states, "... CSRF protections need only be applied to endpoints that will modify information in some way." |

Custom endpoint scripts *must* return a JSON object. The structure of the return object depends on the `method` in the request.

The following example shows the `create` method in the `echo.js` file:

```javascript
if (request.method === "create") {
    return {
        method: "create",
        resourceName: request.resourcePath,
        newResourceId: request.newResourceId,
        parameters: request.additionalParameters,
        content: request.content,
        context: context.current
    }
}
```

The following example shows the `query` method in the `echo.groovy` file:

```groovy
else if (request instanceof QueryRequest) {
    // query results must be returned as a list of maps
    return [
        [
            method: "query",
            resourceName: request.resourcePath,
            pagedResultsCookie: request.pagedResultsCookie,
            pagedResultsOffset: request.pagedResultsOffset,
            pageSize: request.pageSize,
            queryId: request.queryId,
            queryFilter: request.queryFilter.toString(),
            parameters: request.additionalParameters,
            context: context.toJsonValue().getObject()
        ]
    ]
}
```

Depending on the method, the variables available to the script can include the following:

* `resourceName`

  The name of the resource without the `endpoint/` prefix, such as `echo`.

* `newResourceId`

  The identifier of the new object available as the results of a `create` request.

* `revision`

  The revision of the object.

* `parameters`

  Any additional parameters provided in the request. The sample code returns request parameters from an HTTP GET with `?param=x`, as `"parameters":{"param":"x"}`.

* `content`

  Content based on the latest revision of the object, using `getObject`.

* `context`

  The context of the request, including headers and security. For more information, refer to [Request context chain](request-context.html).

* Paging parameters

  The `pagedResultsCookie`, `pagedResultsOffset`, and `pageSize` parameters are specific to `query` methods. For more information refer to [Page Query Results](../objects-guide/queries.html#paging-query-results).

* Query parameters

  The `queryId` and `queryFilter` parameters are specific to `query` methods. For more information, refer to [Construct Queries](../objects-guide/queries.html#constructing-queries).

## Script exceptions

Some custom endpoint scripts require exception-handling logic. To return meaningful messages in REST responses and in logs, you must comply with the language-specific method of throwing errors.

A script written in JavaScript should comply with the following exception format:

```json
throw {
    "code": 400, // any valid HTTP error code
    "message": "custom error message",
    "detail" : {
        "var": parameter1,
        "complexDetailObject" : [
            "detail1",
            "detail2"
        ]
    }
}
```

Exception objects include the specified HTTP error code, the corresponding HTTP error message (such as `Bad Request`), a custom error message, and additional details that may be helpful to determine what actions need to be taken to fix the error.

A script written in Groovy should comply with the following exception format:

```groovy
import org.forgerock.json.resource.ResourceException
import org.forgerock.json.JsonValue

throw new ResourceException(404, "Your error message").setDetail(new JsonValue([
    "var": "parameter1",
    "complexDetailObject" : [
        "detail1",
        "detail2"
    ]
]))
```

## Write an API descriptor for a custom endpoint

Most IDM endpoints are described in the [REST API Explorer](../rest-api-reference/api-explorer.html). Documentation is not generated automatically for custom endpoints.

To generate the documentation for your custom endpoint in the API Explorer, add an `apiDescription` object to your custom endpoint configuration file. The `apiDescription` object includes the following properties:

* `title`

  The endpoint name that expresses its purpose, for example, `Audit`, or `Authentication`.

* `description`

  A description of the endpoint.

* `mvccSupported`

  A Boolean value that indicates whether object versioning is supported. To enable `If-None-Match` or `If-Match` headers in read, delete, and patch requests, this property must be `true`.

* Operations

  An object that describes each operation supported on that endpoint (`create`, `read`, `update`, `delete`, `patch`, `actions`, and `queries`).

* `resourceSchema`

  The schema for the objects at this endpoint.

To refer to examples of the API descriptors included in IDM, log in to the [admin UI](http://localhost:8080/admin), then point your browser to <http://localhost:8080/openidm?_crestapi>.

Compare the descriptors at that URL with what you refer to in the [API Explorer](http://localhost:8080/admin/#apiExplorer).

In addition, the sample configuration file (`openidm/samples/example-configurations/custom-endpoint/conf/endpoint-echo.json`) shows how API descriptors must be constructed:

> **Collapse: Sample API Descriptor Object**
>
> ```json
> {
>     "apiDescription" : {
>         "title" : "Echo",
>         "description" : "Service that echo's your HTTP requests.",
>         "mvccSupported" : false,
>         "create" : {
>             "description" : "Echo a CREATE request.",
>             "mode" : "ID_FROM_SERVER",
>             "singleton" : false
>         },
>         "read" : { "description" : "Echo a READ request." },
>         "update" : { "description" : "Echo an UPDATE request." },
>         "delete" : { "description" : "Echo a DELETE request." },
>         "patch" : {
>             "description" : "Echo a PATCH request.",
>             "operations" : [ "ADD", "REMOVE", "REPLACE", "INCREMENT", "COPY", "MOVE", "TRANSFORM" ]
>         },
>         "actions" : [
>             {
>                 "description" : "Echo an ACTION request.",
>                 "name" : "echo",
>                 "request" : { "type" : "object" },
>                 "response" : {
>                     "title" : "Echo action response",
>                     "type" : "object",
>                     "properties" : {
>                         "method" : {
>                             "type" : "string",
>                             "enum" : [ "action" ]
>                         },
>                         "action" : { "type" : "string" },
>                         "content" : { "type" : "object" },
>                         "parameters" : { "type" : "object" },
>                         "context" : { "type" : "object" }
>                     }
>                 }
>             }
>         ],
>         "queries" : [
>             {
>                 "description" : "Echo a query-filter request.",
>                 "type" : "FILTER",
>                 "queryableFields" : [ "*" ]
>             },
>             {
>                 "description" : "Echo a query-all request.",
>                 "type" : "ID",
>                 "queryId" : "query-all"
>             },
>             {
>                 "description" : "Echo a query-all-ids request.",
>                 "type" : "ID",
>                 "queryId" : "query-all-ids"
>             }
>         ],
>         "resourceSchema" : {
>             "title" : "Echo resource",
>             "type" : "object",
>             "properties" : {
>                 "method" : {
>                     "title" : "CREST method",
>                     "type" : "string"
>                 },
>                 "resourceName" : { "type" : "string" },
>                 "parameters" : { "type" : "object" },
>                 "context" : { "type" : "object" }
>             }
>         }
>     }
> }
> ```
>
> This object generates API documentation in the API explorer that looks like this:
>
> ![echo-api-descriptor](_images/echo-api-descriptor.png)
