Configuration Automation - Ping CLI

Custom API requests

Ping CLI can make API requests to supported Ping Identity products, similar to using curl, using the connector’s api subcommand. For example, pingcli pingone api.

A configured and authenticated service connection is required before running this command. If you have not yet connected a service, refer to Connect Ping Identity services. To authenticate after connecting, use pingcli auth login or the per-product pingcli <connector> auth login command.

Benefits of using Ping CLI over cURL

Using Ping CLI to make direct API requests against a Ping Identity deployment offers a number of benefits over using a tool like curl directly.

Ping CLI manages the API authentication

With commands similar to curl, it is typically up to the developer to provide credentials in the required format or a valid OAuth bearer token with the request. With Ping CLI, the API authentication is managed by the CLI tool and injected into the API request without the developer needing to add it manually. This capability allows the developer to focus on the structure and content of the request and ultimately focus on managing the Ping Identity deployment.

For example, the PingOne API requires a valid OAuth 2.0 bearer token that is typically short lived and can expire. When using Ping CLI, the developer needs to configure the PingOne connection details in the Ping CLI configuration and issue the API request without needing to add any authentication to the request syntax. The Ping CLI tool manages getting a valid access token and, if necessary, the refresh of an existing access token, and adds the token to the request on behalf of the developer.

Ping CLI manages the base URL

With commands similar to curl, it’s typically up to the developer to provide the full URL in the request. This requirement typically means the developer has to create variables for the host name or port to be able to use the same script for development, test, or production. Ping CLI injects the base URL value according to the developer’s CLI configuration, meaning the same requests can be used between multiple environments without the need for custom variables.

Ping CLI performs automatic retries of transient errors

APIs of any hosted or deployed service may respond with transient errors, for example rate limiting, server congestion, or network issues. There are cases when the developer needs to handle these errors and invoke custom logic. However, errors that are considered transient should be retried automatically.

Ping CLI automatically retries transient errors silently to the developer, so if they are encountered on the rare occasion, the developer doesn’t need to retry an API request manually or, if embedded in a script, need to code specific retry logic.

For example, if PingOne returns a 429 HTTP response code as a result of rate limiting, Ping CLI handles the Retry-After header and, if needed, invokes an exponential backoff retry method according to documented best practice on the PingOne API documentation. This repeated request process happens silently to the developer and allows for resilience in scripting environment management.

Ping CLI works with early access and feature-flagged APIs

The api subcommand is not limited to APIs that Ping CLI supports natively. If a Ping Identity product exposes an early access or feature-flagged endpoint that the CLI doesn’t yet have a dedicated command for, it can still be called using the api subcommand. Because authentication and base URL handling are still managed automatically, developers get the same ergonomics for bleeding-edge endpoints as they do for fully-supported ones, with no need to fall back to curl.

Making a request

The format for making a request is pingcli <connector> api [flags] API_URI, where <connector> is the name of the connected product. For example:

  • pingcli pingone api <uri>: requests against the PingOne management API

The API_URI is required and must be the API URI path omitting the base API path of the service. For example, with PingOne, a full URL might be documented in the API documentation as:

{{apiPath}}/environments/{{envID}}/applications

Since Ping CLI injects the apiPath value, the API_URI value to provide is environments/{{envID}}/applications. For example:

pingcli pingone api environments/$ENVIRONMENT_ID/applications

Available flags

Flag Short form Description

--http-method

-m

The HTTP method to use for the request. (default GET) Options are: GET, POST, PUT, PATCH, DELETE.

--data

(none)

Path to a JSON file containing the request body. Required for POST, PUT, and PATCH requests.

--data-raw

(none)

Inline JSON string to send as the request body. An alternative to --data when you don’t want to create a file. Example: --data-raw '{"name": "My environment"}'.

--header

-r

A custom header to add to the request. Repeatable for multiple headers. Example: --header "Content-Type: application/vnd.pingidentity.user.import+json".

--output-format

-O

The console output format. Options are: json, ndjson, text. (default text)

--fail

-f

Return a non-zero exit code when the HTTP response indicates a failure status (4xx or 5xx).

Example GET request

The following example reads all applications for a PingOne environment.

pingcli pingone api \
  environments/$PINGONE_ENVIRONMENT_ID/applications
Expand example response
{
  "schemaVersion": "1.0",
  "status": "success",
  "message": "Custom request successful",
  "data": {
    "_links": {
      "self": {
        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/applications"
      }
    },
    "_embedded": {
      "applications": [
        {
          "_links": {
            "environment": {
              "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
            },
            "self": {
              "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/applications/fb955fe0-9b00-4bd3-a64e-6054dc95eae3"
            }
          },
          "environment": {
            "id": "abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
          },
          "id": "fb955fe0-9b00-4bd3-a64e-6054dc95eae3",
          "name": "PingOne Admin Console",
          "enabled": true,
          "type": "PING_ONE_ADMIN_CONSOLE",
          "protocol": "OPENID_CONNECT"
        }
      ]
    },
    "size": 1
  },
  "errors": [],
  "meta": {
    "command": "pingcli pingone api",
    "api": { "httpStatus": 200 }
  }
}

Example POST request (using data from a file)

The following example creates a new application in a PingOne environment. The API request payload is supplied from a JSON file.

pingcli pingone api \
  -m POST \
  --data ./my-application.json \
  environments/$PINGONE_ENVIRONMENT_ID/applications
Expand my-application.json contents
{
    "enabled": true,
    "name": "OIDC-Web-App",
    "description": "Test Description - OIDC App (Web)",
    "type": "WEB_APP",
    "protocol": "OPENID_CONNECT",
    "homePageUrl": "https://example.com/homePage",
    "loginPageUrl": "https://example.com/loginPage",
    "grantTypes": [
        "AUTHORIZATION_CODE"
    ],
    "redirectUris": [
        "https://example.com"
    ],
    "responseTypes": [
        "CODE"
    ],
    "tokenEndpointAuthMethod": "CLIENT_SECRET_BASIC",
    "pkceEnforcement": "REQUIRED"
}
Expand example response
{
  "schemaVersion": "1.0",
  "status": "success",
  "message": "Custom request successful",
  "data": {
    "_links": {
      "self": {
        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/applications/403b2bb3-a151-42b2-99d5-5eae2b151215"
      }
    },
    "environment": {
      "id": "abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
    },
    "id": "403b2bb3-a151-42b2-99d5-5eae2b151215",
    "name": "OIDC-Web-App",
    "enabled": true,
    "type": "WEB_APP",
    "protocol": "OPENID_CONNECT"
  },
  "errors": [],
  "meta": {
    "command": "pingcli pingone api",
    "api": { "httpStatus": 201 }
  }
}

Example POST request (using inline data)

The following example creates a new PingOne environment using inline JSON data. The --data-raw flag is useful in scripts when creating a temporary file is not desirable.

pingcli pingone api \
  -m POST \
  --data-raw '{"name": "My New Environment", "region": {"id": "NA"}, "type": "SANDBOX"}' \
  environments
Expand example response
{
  "schemaVersion": "1.0",
  "status": "success",
  "message": "Custom request successful",
  "data": {
    "_links": {
      "self": {
        "href": "https://api.pingone.com/v1/environments/c1e92b4a-3d7e-4f1a-a2c5-9b8d0e6f3a21"
      }
    },
    "id": "c1e92b4a-3d7e-4f1a-a2c5-9b8d0e6f3a21",
    "name": "My New Environment",
    "region": {
      "id": "NA"
    },
    "type": "SANDBOX"
  },
  "errors": [],
  "meta": {
    "command": "pingcli pingone api",
    "api": { "httpStatus": 201 }
  }
}

Example POST request (using a custom header)

Some PingOne APIs require a specific Content-Type header. The following example imports users using the bulk user import API, which requires a custom content type header.

pingcli pingone api \
  -m POST \
  --header "Content-Type: application/vnd.pingidentity.user.import+json" \
  --data ./users.json \
  environments/$PINGONE_ENVIRONMENT_ID/users

Example PUT request (using data from a file)

The following example updates an existing application in a PingOne environment.

pingcli pingone api \
  -m PUT \
  --data ./my-application.json \
  environments/$PINGONE_ENVIRONMENT_ID/applications/$PINGONE_MY_APPLICATION_ID
Expand my-application.json contents
{
    "name": "AppName",
    "description": "This is my UPDATED app description",
    "protocol": "OPENID_CONNECT",
    "type": "WEB_APP"
}
Expand example response
{
  "schemaVersion": "1.0",
  "status": "success",
  "message": "Custom request successful",
  "data": {
    "_links": {
      "self": {
        "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/applications/d64c5a69-51ed-4c73-b8bc-8a3fafa6d0ea"
      }
    },
    "environment": {
      "id": "abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
    },
    "id": "d64c5a69-51ed-4c73-b8bc-8a3fafa6d0ea",
    "name": "AppName",
    "description": "this is my UPDATED app description",
    "enabled": false,
    "type": "WEB_APP",
    "protocol": "OPENID_CONNECT"
  },
  "errors": [],
  "meta": {
    "command": "pingcli pingone api",
    "api": { "httpStatus": 200 }
  }
}

Example DELETE request

The following example deletes an application from a PingOne environment.

pingcli pingone api \
  -m DELETE \
  environments/$PINGONE_ENVIRONMENT_ID/applications/$PINGONE_APPLICATION_ID
Expand example response
{
  "schemaVersion": "1.0",
  "status": "success",
  "message": "Custom request successful",
  "data": null,
  "errors": [],
  "meta": {
    "command": "pingcli pingone api",
    "api": { "httpStatus": 204 }
  }
}

Example: filtering output in scripts

Use -O json to get machine-readable JSON output and pipe it to jq to extract specific fields. With -O json, the API response body is in the data field of the output envelope. This is useful in automation scripts where you need to extract a specific field for use in a subsequent command.

The following example lists all applications and extracts just their IDs.

pingcli pingone api \
  -O json \
  environments/$PINGONE_ENVIRONMENT_ID/applications \
  | jq -r '.data._embedded.applications[].id'

Use --fail to cause the command to return a non-zero exit code on any 4xx or 5xx response, making it compatible with set -e in shell scripts. When a request fails, the envelope’s errors array contains structured error details.

set -e

pingcli pingone api \
  -O json \
  --fail \
  environments/$PINGONE_ENVIRONMENT_ID/applications \
  | jq -r '.data._embedded.applications[].id'