By the end of this tutorial, you will have a working custom platform script that connects to a REST API over HTTP or HTTPS, verifies API connectivity with CheckSystem, validates managed account credentials with CheckPassword, and rotates the password with ChangePassword.
You will build a minimal custom platform script with three operations:
CheckSystem— makes an authenticated HTTP request to verify the service account can reach the API.CheckPassword— makes an authenticated HTTP request to verify the managed account credentials are valid.ChangePassword— authenticates as the service account and sends a password-update request for the managed account.
This is intentionally small. It is the quickest way to get from zero to a working HTTP platform that can test connectivity, validate credentials, and perform a basic password rotation.
Before you start, make sure you have:
- A target system with a REST API that supports authentication such as Basic Auth.
- An SPP appliance and the
safeguard-psPowerShell module. If you have not used that workflow before, read Development Workflow. - Basic familiarity with JSON and REST APIs.
Create a new file named MyFirstHttpPlatform.json and start with this minimal structure:
{
"Id": "MyFirstHttpPlatform",
"BackEnd": "Scriptable",
"CheckSystem": {
"Parameters": [],
"Do": []
}
}The basics are the same as SSH:
Id— the internal identifier for the script.BackEnd— always"Scriptable"for a custom platform script.CheckSystem— one operation in your script. Each operation has aParametersarray and aDoblock.
Think of Parameters as the inputs SPP passes to the operation and Do as the list of commands the script engine runs.
Next, define the values CheckSystem needs to connect to the API:
"CheckSystem": {
"Parameters": [
{ "Address": { "Type": "String", "Required": true } },
{ "FuncUsername": { "Type": "String", "Required": true } },
{ "FuncPassword": { "Type": "Secret", "Required": true } },
{ "UseSsl": { "Type": "Boolean", "Required": false, "DefaultValue": true } }
],
"Do": []
}Here is what each parameter does:
Address— the target hostname or IP address. SPP auto-populates this from the asset's network address.FuncUsername/FuncPassword— the service account credentials on the asset. SPP auto-populates these whenCheckSystemruns.UseSsl— a custom parameter that lets you switch between HTTPS and HTTP.
For a first HTTP script, these four parameters are enough to prove the API is reachable and the credentials work.
Now add the actual HTTP workflow. HTTP-based platforms usually follow the same pattern every time:
- Set a base address.
- Create a request object.
- Add authentication.
- Send the request.
- Inspect the response.
Build it one piece at a time.
BaseAddress tells the script engine the root URL for all requests:
{ "BaseAddress": { "Address": "https://%Address%" } }This first example assumes HTTPS so you can see the simplest possible form. In the assembled block below, you will make it respect UseSsl so the same script can work with either HTTPS or HTTP.
NewHttpRequest initializes a reusable request object:
{ "NewHttpRequest": { "ObjectName": "ApiRequest" } }HttpAuth attaches credentials to the request:
{ "HttpAuth": { "RequestObjectName": "ApiRequest", "Type": "Basic", "Credentials": { "Login": "%FuncUsername%", "Password": "%FuncPassword%" } } }Request executes the HTTP call:
{
"Request": {
"RequestObjectName": "ApiRequest",
"ResponseObjectName": "ApiResponse",
"Verb": "GET",
"Url": "/api/status",
"Content": {
"ContentType": "application/json"
}
}
}ResponseObjectName is important because it stores the result so later commands can inspect StatusCode, headers, and response content.
Use a Condition to verify the call succeeded:
{
"Condition": {
"If": "ApiResponse.StatusCode.ToString() == \"200\"",
"Then": {
"Do": [
{ "Return": { "Value": true } }
]
},
"Else": {
"Do": [
{ "Throw": { "Value": "CheckSystem failed: HTTP %{ApiResponse.StatusCode.ToString()}%" } }
]
}
}
}Here is the full Do block assembled together. This version uses UseSsl so the same script works with either https:// or http://:
"Do": [
{
"Condition": {
"If": "UseSsl",
"Then": {
"Do": [
{ "BaseAddress": { "Address": "https://%Address%" } }
]
},
"Else": {
"Do": [
{ "BaseAddress": { "Address": "http://%Address%" } }
]
}
}
},
{ "NewHttpRequest": { "ObjectName": "ApiRequest" } },
{ "HttpAuth": { "RequestObjectName": "ApiRequest", "Type": "Basic", "Credentials": { "Login": "%FuncUsername%", "Password": "%FuncPassword%" } } },
{
"Request": {
"RequestObjectName": "ApiRequest",
"ResponseObjectName": "ApiResponse",
"Verb": "GET",
"Url": "/api/status",
"Content": {
"ContentType": "application/json"
}
}
},
{
"Condition": {
"If": "ApiResponse.StatusCode.ToString() == \"200\"",
"Then": {
"Do": [
{ "Return": { "Value": true } }
]
},
"Else": {
"Do": [
{ "Throw": { "Value": "CheckSystem failed: HTTP %{ApiResponse.StatusCode.ToString()}%" } }
]
}
}
}
]This pattern is the core of most simple HTTP platforms:
BaseAddressdefines the root URL.NewHttpRequestcreates a request object you can configure.HttpAuthadds the authentication details.Requestsends the call and stores the response.Conditionturns the HTTP status code into a clear success or failure.
At this point, your script proves one thing: the service account can reach the API and authenticate successfully.
Next, add a second operation that uses the managed account credentials instead of the service account credentials:
"CheckPassword": {
"Parameters": [
{ "Address": { "Type": "String", "Required": true } },
{ "AccountUserName": { "Type": "String", "Required": true } },
{ "AccountPassword": { "Type": "Secret", "Required": true } },
{ "UseSsl": { "Type": "Boolean", "Required": false, "DefaultValue": true } }
],
"Do": [
{ "BaseAddress": { "Address": "https://%Address%" } },
{ "NewHttpRequest": { "ObjectName": "ApiRequest" } },
{ "HttpAuth": { "RequestObjectName": "ApiRequest", "Type": "Basic", "Credentials": { "Login": "%AccountUserName%", "Password": "%AccountPassword%" } } },
{
"Request": {
"RequestObjectName": "ApiRequest",
"ResponseObjectName": "ApiResponse",
"Verb": "GET",
"Url": "/api/users/me",
"Content": {
"ContentType": "application/json"
}
}
},
{
"Condition": {
"If": "ApiResponse.StatusCode.ToString() == \"200\"",
"Then": {
"Do": [
{ "Return": { "Value": true } }
]
},
"Else": {
"Do": [
{ "Throw": { "Value": "CheckPassword failed: HTTP %{ApiResponse.StatusCode.ToString()}%" } }
]
}
}
}
]
}What changed:
CheckPasswordusesAccountUserNameandAccountPassword, which SPP auto-populates from the managed account.- It calls a user-specific endpoint such as
/api/users/meto confirm the credentials work for that account. - A
200response means the password is valid. Any other status means the check failed.
Just like CheckSystem, the final combined script below uses UseSsl to choose HTTPS or HTTP for this operation too.
ChangePassword is the operation SPP uses when it rotates a managed account password. For a REST API, that usually means the service account authenticates first and then sends an administrative request to update the managed account's password.
Unlike CheckPassword, which authenticates as the managed account to prove the existing password works, ChangePassword usually authenticates as the service account because changing another user's password normally requires admin privileges.
Add this operation:
"ChangePassword": {
"Parameters": [
{ "Address": { "Type": "String", "Required": true } },
{ "FuncUsername": { "Type": "String", "Required": true } },
{ "FuncPassword": { "Type": "Secret", "Required": true } },
{ "AccountUserName": { "Type": "String", "Required": true } },
{ "NewPassword": { "Type": "Secret", "Required": true } },
{ "UseSsl": { "Type": "Boolean", "Required": false, "DefaultValue": true } }
],
"Do": [
{
"Condition": {
"If": "UseSsl",
"Then": { "Do": [{ "BaseAddress": { "Address": "https://%Address%" } }] },
"Else": { "Do": [{ "BaseAddress": { "Address": "http://%Address%" } }] }
}
},
{ "NewHttpRequest": { "ObjectName": "ChangeRequest" } },
{ "HttpAuth": { "RequestObjectName": "ChangeRequest", "Type": "Basic", "Credentials": { "Login": "%FuncUsername%", "Password": "%FuncPassword%" } } },
{
"Request": {
"RequestObjectName": "ChangeRequest",
"ResponseObjectName": "ChangeResponse",
"Verb": "PUT",
"Url": "/api/users/%AccountUserName%/password",
"Content": {
"ContentType": "application/json",
"Body": "{\"password\": \"%NewPassword%\"}"
}
}
},
{
"Condition": {
"If": "ChangeResponse.StatusCode.ToString() == \"200\" || ChangeResponse.StatusCode.ToString() == \"204\"",
"Then": { "Do": [{ "Return": { "Value": true } }] },
"Else": { "Do": [{ "Throw": { "Value": "ChangePassword failed: HTTP %{ChangeResponse.StatusCode.ToString()}%" } }] }
}
}
]
}Key points:
FuncUsername/FuncPasswordare the service-account credentials used for the admin API call.AccountUserNameidentifies the managed account whose password SPP is rotating.NewPasswordis the new password value generated by SPP and passed into the operation.- This simplified admin-driven example does not send
AccountPassword. If your API requires the old password too, addAccountPasswordand include it in the request body.
The Do block follows the standard HTTP password-change pattern:
- Choose
https://orhttp://withUseSsl. - Create a new request object.
- Authenticate the request with Basic Auth using the service account.
- Send a
PUTrequest to/api/users/%AccountUserName%/password. - Treat
200or204as success and throw a clear error for anything else.
The JSON request body is constructed inline as a string:
"Body": "{\"password\": \"%NewPassword%\"}"The %NewPassword% variable is expanded before the request is sent, so the API receives real JSON such as {"password": "N3wP@ssw0rd!"}. Many APIs use a slightly different shape, such as including both the old and new password in the body.
Some APIs use POST instead of PUT, or require a different body format. The pattern stays the same: authenticate as the admin, send the password-change request, and verify the success status code.
Here is the full script with all three operations in one file:
{
"Id": "MyFirstHttpPlatform",
"BackEnd": "Scriptable",
"CheckSystem": {
"Parameters": [
{ "Address": { "Type": "String", "Required": true } },
{ "FuncUsername": { "Type": "String", "Required": true } },
{ "FuncPassword": { "Type": "Secret", "Required": true } },
{ "UseSsl": { "Type": "Boolean", "Required": false, "DefaultValue": true } }
],
"Do": [
{
"Condition": {
"If": "UseSsl",
"Then": {
"Do": [
{ "BaseAddress": { "Address": "https://%Address%" } }
]
},
"Else": {
"Do": [
{ "BaseAddress": { "Address": "http://%Address%" } }
]
}
}
},
{ "NewHttpRequest": { "ObjectName": "ApiRequest" } },
{ "HttpAuth": { "RequestObjectName": "ApiRequest", "Type": "Basic", "Credentials": { "Login": "%FuncUsername%", "Password": "%FuncPassword%" } } },
{
"Request": {
"RequestObjectName": "ApiRequest",
"ResponseObjectName": "ApiResponse",
"Verb": "GET",
"Url": "/api/status",
"Content": {
"ContentType": "application/json"
}
}
},
{
"Condition": {
"If": "ApiResponse.StatusCode.ToString() == \"200\"",
"Then": {
"Do": [
{ "Return": { "Value": true } }
]
},
"Else": {
"Do": [
{ "Throw": { "Value": "CheckSystem failed: HTTP %{ApiResponse.StatusCode.ToString()}%" } }
]
}
}
}
]
},
"CheckPassword": {
"Parameters": [
{ "Address": { "Type": "String", "Required": true } },
{ "AccountUserName": { "Type": "String", "Required": true } },
{ "AccountPassword": { "Type": "Secret", "Required": true } },
{ "UseSsl": { "Type": "Boolean", "Required": false, "DefaultValue": true } }
],
"Do": [
{
"Condition": {
"If": "UseSsl",
"Then": {
"Do": [
{ "BaseAddress": { "Address": "https://%Address%" } }
]
},
"Else": {
"Do": [
{ "BaseAddress": { "Address": "http://%Address%" } }
]
}
}
},
{ "NewHttpRequest": { "ObjectName": "ApiRequest" } },
{ "HttpAuth": { "RequestObjectName": "ApiRequest", "Type": "Basic", "Credentials": { "Login": "%AccountUserName%", "Password": "%AccountPassword%" } } },
{
"Request": {
"RequestObjectName": "ApiRequest",
"ResponseObjectName": "ApiResponse",
"Verb": "GET",
"Url": "/api/users/me",
"Content": {
"ContentType": "application/json"
}
}
},
{
"Condition": {
"If": "ApiResponse.StatusCode.ToString() == \"200\"",
"Then": {
"Do": [
{ "Return": { "Value": true } }
]
},
"Else": {
"Do": [
{ "Throw": { "Value": "CheckPassword failed: HTTP %{ApiResponse.StatusCode.ToString()}%" } }
]
}
}
}
]
},
"ChangePassword": {
"Parameters": [
{ "Address": { "Type": "String", "Required": true } },
{ "FuncUsername": { "Type": "String", "Required": true } },
{ "FuncPassword": { "Type": "Secret", "Required": true } },
{ "AccountUserName": { "Type": "String", "Required": true } },
{ "NewPassword": { "Type": "Secret", "Required": true } },
{ "UseSsl": { "Type": "Boolean", "Required": false, "DefaultValue": true } }
],
"Do": [
{
"Condition": {
"If": "UseSsl",
"Then": { "Do": [{ "BaseAddress": { "Address": "https://%Address%" } }] },
"Else": { "Do": [{ "BaseAddress": { "Address": "http://%Address%" } }] }
}
},
{ "NewHttpRequest": { "ObjectName": "ChangeRequest" } },
{ "HttpAuth": { "RequestObjectName": "ChangeRequest", "Type": "Basic", "Credentials": { "Login": "%FuncUsername%", "Password": "%FuncPassword%" } } },
{
"Request": {
"RequestObjectName": "ChangeRequest",
"ResponseObjectName": "ChangeResponse",
"Verb": "PUT",
"Url": "/api/users/%AccountUserName%/password",
"Content": {
"ContentType": "application/json",
"Body": "{\"password\": \"%NewPassword%\"}"
}
}
},
{
"Condition": {
"If": "ChangeResponse.StatusCode.ToString() == \"200\" || ChangeResponse.StatusCode.ToString() == \"204\"",
"Then": { "Do": [{ "Return": { "Value": true } }] },
"Else": { "Do": [{ "Throw": { "Value": "ChangePassword failed: HTTP %{ChangeResponse.StatusCode.ToString()}%" } }] }
}
}
]
}
}This combined version is still minimal, but it shows the full HTTP request and response patterns you will reuse in more advanced scripts.
Validate the script locally first, then create the custom platform in SPP:
Test-SafeguardCustomPlatformScript ".\MyFirstHttpPlatform.json"
New-SafeguardCustomPlatform -Name "My First HTTP Platform" -ScriptFile ".\MyFirstHttpPlatform.json"If validation fails, fix the JSON before you upload anything.
Once the platform exists, create a test asset and a test account:
New-SafeguardCustomPlatformAsset "My First HTTP Platform" "api.example.com" -ServiceAccountCredentialType Password -ServiceAccountName "admin"
New-SafeguardAssetAccount "api.example.com" "testuser"
Set-SafeguardAssetAccountPassword -AssetToUse "api.example.com" -AccountToUse "testuser"In this example, admin is the service account used by CheckSystem and ChangePassword, and testuser is the managed account validated by CheckPassword and updated by ChangePassword.
Now run the connectivity check, the account-password check, and finally a password change while you inspect the task log output:
Test-SafeguardAsset "api.example.com" -ExtendedLogging
Test-SafeguardAssetAccountPassword "api.example.com" "testuser" -ExtendedLogging
Invoke-SafeguardAssetAccountPasswordChange -AssetToUse "api.example.com" -AccountToUse "testuser"
Get-SafeguardTaskLogStart with Test-SafeguardAsset. If CheckSystem fails, fix connectivity or service-account issues before you test CheckPassword. Once both read-only checks work, run Invoke-SafeguardAssetAccountPasswordChange to exercise ChangePassword.
When an HTTP platform fails, start with the status code and connection details:
- SSL certificate errors — add
"IgnoreServerCertAuthentication": trueto theRequestwhile developing against self-signed certificates, then remove or disable it for production use. 401 Unauthorized— the credentials are wrong or the authentication type does not match what the API expects.403 Forbidden— the service account authenticated, but it does not have permission to change another user's password.404 Not Found— the URL path is wrong. Verify the endpoint in the API documentation.- Connection refused — check the address and confirm whether the target expects HTTPS or HTTP.
- Timeout — there may be a network access issue or the API may be slow. If needed, extend the script later with timeout handling.
During development, always run tests with -ExtendedLogging and review Get-SafeguardTaskLog so you can see where the request failed.
The example uses generic placeholder endpoints. To make it work with a real target:
- Change
/api/status,/api/users/me, and/api/users/%AccountUserName%/passwordto the actual endpoints your API exposes. - Switch the authentication type if needed, such as Bearer tokens or OAuth2. See the HTTP Platforms Guide.
- Add custom parameters for API-specific values such as a base path, tenant ID, or API version.
- Adjust the password-change verb or request body if your API uses
POST, expects the old password, or wraps the new password in a different JSON structure. - Handle pagination, token exchange, or multi-step authentication flows for more complex APIs.
Once this minimal script works, you can build on it:
- Add account discovery so SPP can onboard accounts automatically from the target system.
- Implement Bearer token authentication for OAuth2-based APIs.
- Add API-specific parameters such as a tenant ID, API version, or base path.
- Adapt
ChangePasswordto APIs that require the current password or a follow-up verification call. - Explore the real-world
WordPressHttp.jsonsample. - Read the Operations Reference.