The external services API provides endpoints to configure integrations between PingOne and a customer’s external services. An external service resource in PingOne defines all requests, variables, and secrets related to the external service integration, and it processes the response returned by the external service.

Currently, the service calls externally hosted custom code using a defined set of inputs and waits for a JSON response from the external service. If certain elements of the request configuration submitted to the external service need to be generated dynamically at the time of request invocation, developers should use the Ping Expression Language (PEL) to reference these dynamic values. For more information about PEL, see Ping Expression Language Specification.

At this time, responses returned from external services must be in JSON format. However, there is no specific JSON structure requirement. The response JSON can be mapped to another JSON structure for use with other PingOne platform APIs.

Rate limits

It is expected that all external services manage their rate limits. The PingOne external services API does not enforce any rate limiting other than load balancing to prevent one tenant from using all available requests. It ensures that available request threads are shared. This service respects 429 Too Many Requests errors and stops retrying. It is assumed that the external services have rate limiting that allows them to differentiate per customer.

External service configuration (generic example)

The following external service configuration shows how to define the variables, authSchemes, requests, and response properties for the external service resource. Code comments provide additional detailed explanations of each property configuration.

{
   "name": "external_service_{{$timestamp}}",
   "description": "An external service",
   "variables": {
       "variable1": "variable1Value"
   },
   "#comment": "All authSchemes are encrypted in storage. Auth scheme values are PEL statements. They can reference variables, secrets, and inputs.",
   "authSchemes": [
       {
           "name": "Auth Scheme 1",
           "type": "API_KEY",
           "key": "'SampleKey'"
       },
       {
           "name": "Api Key Scheme to show inputs usage",
           "type": "API_KEY",
           "key": "inputs.apiKey"
       },
       {
           "name": "Third Api Key Scheme to show secrets usage",
           "type": "API_KEY",
           "key": "secrets.apiKey"
       },
       {
           "name": "Fourth Api Key Scheme to show variables usage",
           "type": "API_KEY",
           "key": "variables.apiKeyVariable"
       },
       {
           "name": "Auth Scheme 2",
           "type": "BASIC",
           "username": "SampleUsername",
           "password": "SamplePassword"
       },
       {
           "name": "Auth Scheme 3",
           "type": "BEARER",
           "token": "SampleToken"
       },
       {
           "name": "Auth Scheme 4",
           "type": "OAUTH_2",
           "tokenEndpointAuthType": "client_secret_basic",
           "#comment": "scope is optional",
           "scope": [ "scope" ],
           "tokenEndpoint": "https://example.com",
           "clientId": "SampleClientId",
           "clientSecret": "SampleClientSecret"
       }
   ],
   "requests": [
       {
           "name": "request_1_{{$timestamp}}",
           "description": "External service request configuration",
           "request": {
               "method": "GET",
               "url": "'https://example.com'"
           }
       },
       {
           "name": "request_2_{{$timestamp}}",
           "description": "External service request configuration",
           "request": {
               "method": "POST",
               "url": "inputs.url",
               "authScheme": "Auth Scheme 1",
               "headers": {
                   "staticHeader": "'staticValue'",
                   "dynamicHeader": "inputs.dynamicHeaderValue"
               },
               "body": {
                   "#comment": "Request URL, headers, and body can reference inputs, variables, and secrets. This demonstrates that.",
                   "template": "{ 'staticProperty' : 200, 'dynamicProperty': inputs.dynamicBodyProperty, 'fromVariable': variables.variable1, 'fromSecret': secrets.secret1 }",
                   "#comment": "inputSchema is optional",
                   "inputSchema": "<insert json schema to be used elsewhere in the platform>"
               }
           },
           "#comment": "A reponse can be mapped to encapsulate an API call's complexity through a PEL expression. The PEL expression has access to an object 'response' that has three properties: status, headers, and body. Any JSON path into the response is valid. For example: response.body.someProperty. If a path cannot be found, it will resolve to null.",
           "response": {
               "output": "{ 'message': 'This is a custom response mapping', 'responseStatus': response.status, 'responseHeaders': response.headers, 'responseBody': response.body }",
               "#comment": "outputSchema is optional",
               "outputSchema": "<insert json schema to be used elsewhere in the platform>"
           }
       }
   ]
}

External service configuration and invocation walkthrough

The following example shows how to create a basic external service configuration resource and initiate a request to the external service. In this example, the external service is a third-party risk score evaluator.

Step 1: To create the external service resource, send a POST request to {{apiPath}}/environments/{{envID}}/externalServices. The request body for the external service configuration defines a requests property that specifies the query to the external service. The requests property includes parameters to authenticate with the external service, specify the HTTP request method and body, and if needed, specify input and output JSON schema to use the response data with other PingOne platform requests. The request body for the query to the external service specifies a userId as the input.

{
   "name": "Risk-Score-Test",
   "description": "A risk score service",
   "variables": {
       "host": "https://riskScore.com"
   },
   "authSchemes": [
       {
           "name": "Basic Auth",
           "type": "BASIC",
           "username": "riskUser1",
           "password": "myPassword"
       }
   ],
   "requests": [
       {
          
           "name": "Get_Risk_Score",
           "displayName": "Risk Score Generator",
           "description": "A risk score generator",
           "request": {
               "authScheme": "Basic Auth",
               "method": "POST",
               "url": "variables.host + /getRiskScore",
               "headers": {
                   "Content-Type": "application/json"
               },
               "body": {
                   "type": "JSON",
                   "template": "{ \"userId\": inputs.userId }"
               },
               "inputSchema": "<insert JSON schema to be used elsewhere in the platform>"
           },
           "response": {
               "output": "{\"status\": + response.status, \"headers\": + response.headers, \"body\": + response.body}",
               "outputSchema": "<insert JSON schema to be used elsewhere in the platform>"
           }
       }
   ]
}

Step 2: To initiate the request to the external service, send a POST request to /environments/{{envID}}/externalServices/{{externalServiceID}}/requests/{{requestName}}. In this example, the {{requestName}} is the name attribute specified in the requests property configuration in Step 1, which is Get_Risk_Score. The request body specifies the input properties. In this case, there is only the userId input property.

{
    "inputs": {
        "userId": "{{userID}}"
    }
}

The response returns the data from the external service in the output format.

{
 "outputs": {
   "status": 200,
   "headers": {
     "Content-Type": "application/json"
   },
   "body": {
     "riskScore": 98
   }
 }
}