This document describes how to use Splashtop Secure Workspace API automation to create, enable, validate, and remove Private Applications programmatically.
Use case
Some deployment workflows create short-lived preview environments with unique hostnames, for example:
preview-<SHA>.example.com
Because Secure Workspace applications do not support wildcard application URLs, each preview hostname must be registered as its own Private Application. Instead of creating these applications manually, a CI/CD pipeline can call the Secure Workspace APIs during provisioning and cleanup.
Recommended workflow:
- CI/CD deploys the preview environment.
- CI/CD creates a matching Secure Workspace Private Application.
- CI/CD assigns the application to the expected connector, edge, network, and user groups.
- CI/CD enables the application.
- Optional: CI/CD validates access with the
sdpccommand-line client. - When the preview environment is removed, CI/CD deletes the Secure Workspace application.
API capabilities
The automation flow uses these endpoints:
| Step | API | Purpose |
|---|---|---|
| Authenticate API key | POST /iam/v1/apikey/authenticate |
Exchange an API key for SDP and IAM tokens. |
| Import application | POST /ztna/v1/application/import_json |
Create Private Applications from an exported/import JSON payload. |
| Export application | POST /ztna/v1/application/export_json |
Export application definitions. Useful for templates and validation. |
| Get application by name | GET /ztna/v1/application/by_name?app_name={app_name} |
Find an existing application by its unique application name. |
| Check application name | POST /ztna/v1/application/check_name |
Check whether one or more application names are already in use. |
| Enable or disable application | PUT /ztna/v1/application/{app_id}/enabled |
Enable or disable application access. |
| Delete application | DELETE /ztna/v1/application/{app_id} |
Remove a Private Application. |
| Bulk delete applications | DELETE /ztna/v1/bulkDeleteApplication |
Remove multiple applications by ID. |
The script uses the import/export JSON APIs because they provide a practical way to clone a known-good application definition and replace the environment-specific fields.
Required API key permissions
Create a dedicated API key for automation. Grant only the permissions required by the pipeline.
Minimum permissions for the full lifecycle:
| Permission | Required for |
|---|---|
ImportApplication |
Creating applications with import_json. |
ExportApplication |
Exporting application templates or validating created applications. |
EnableApplication |
Enabling or disabling application access. |
RemoveApplication |
Deleting applications during cleanup. |
DesktopConnectApplication |
Optional, only if the pipeline validates connectivity with sdpc. |
DesktopGetApplication / DesktopListApplication |
Optional, only if validation needs desktop application lookup/list behavior. |
Store the API key in the CI/CD secret manager. Do not commit it to source control.
Inputs required by the pipeline
The pipeline needs these values:
| Input | Description | Example |
|---|---|---|
SSW_SERVER_URL |
Secure Workspace server hostname. | qaautotest.us.ssw.splashtop.com |
SSW_API_KEY |
API key with the permissions listed above. | Stored as a CI/CD secret. |
ORG_NAME |
Organization name. The script accepts this argument, although the organization is normally resolved from the server URL and API key context. | qaautotest |
ENV |
Runtime environment used by the test script when downloading sdpc. |
prod |
CONNECTOR_NAME |
Connector where the Private Application should be assigned. | prod-qa-auto-testing-kind-connector |
EDGE_NAME |
Edge name used by the application. | Portland, Oregon |
NETWORK_NAME |
Network name shown in the application definition. | qaautotest |
APP_NAME |
Unique internal application name. | preview-a1b2c3d4 |
DISPLAY_NAME |
User-facing application name. | Preview App a1b2c3d4 |
TARGET_HOST |
Preview environment hostname or internal service DNS name. | preview-a1b2c3d4.example.com |
TARGET_PORT |
Application port. | 443 |
PROTOCOL |
Application protocol. | https, http, ssh, etc. |
GROUP_NAMES |
Groups allowed to access the application. | ["_alluser"] or a customer-specific group. |
Available production edge names:
Ashburn, VirginiaPortland, OregonFrankfurt, GermanyLondon, EnglandTokyo, JapanHongKong, ChinaTaipei, Taiwan
Example test-script invocation:
sudo bash -x auto_app_testing.sh "${TEST_APIKEY}" \
qaautotest.us.ssw.splashtop.com \
qaautotest \
prod \
prod-qa-auto-testing-kind-connector \
qaautotest \
"Portland, Oregon"
Authentication
Exchange the API key for an SDP token:
curl --location --request POST "https://${SSW_SERVER_URL}/iam/v1/apikey/authenticate" \
--header "Content-Type: application/json" \
--data "{\"apikey\":\"${SSW_API_KEY}\"}"
The response includes:
{
"data": {
"sdp_token": "<SDP_TOKEN>",
"iam_token": "<IAM_TOKEN>"
}
}
Use data.sdp_token as the bearer token for application management APIs:
Authorization: Bearer <SDP_TOKEN>
Application import payload
The import API expects a multipart request with:
| Form field | Description |
|---|---|
connector |
Connector name to assign to the imported application. |
edge |
Edge name to assign to the imported application. |
inclusion_list[] |
Optional list of application names to import from the file. Leave empty to import all apps in the file. |
exclusion_list[] |
Optional list of application names to skip. |
file |
JSON file containing app_data_list. |
Example import JSON:
{
"export_metadata": {
"tenant_name": "example-tenant",
"org_name": "example-org",
"user_name": "automation",
"export_time": "2026-06-26T00:00:00Z"
},
"app_name_list": [
"preview-a1b2c3d4"
],
"app_data_list": [
{
"name": "preview-a1b2c3d4",
"display_name": "Preview App a1b2c3d4",
"edge_name": "Portland, Oregon",
"site_name": "prod-qa-auto-testing-kind-connector",
"site_display_name": "prod-qa-auto-testing-kind-connector",
"network_name": "qaautotest",
"protocol": "https",
"group_names": [
"_alluser"
],
"hosts": [
{
"host": "preview-a1b2c3d4.example.com",
"port": 443
}
],
"host": "preview-a1b2c3d4.example.com",
"port": 443,
"ports": [
443
],
"ip": "",
"access_type": "all",
"app_web_config": "{\"host\":\"preview-a1b2c3d4.example.com\",\"port\":\"443\",\"via_private_network\":true,\"recording\":true}",
"icon": "web",
"via_private_network": true,
"enabled": true,
"dynamic_target_flag": false
}
]
}
Notes:
namemust be unique within the organization. The import API is not an upsert API. If an application with the same name already exists, import fails.connectorandedgein the multipart form can override the values in the JSON file.group_namesmust reference groups that exist in the organization. If a group is missing, the import result may include an error message for that application.app_web_configis a JSON string, not a nested JSON object.- For private applications, set
via_private_networktotrue.
Create application
Create the application with import_json:
curl --location --request POST "https://${SSW_SERVER_URL}/ztna/v1/application/import_json" \
--header "accept: application/json" \
--header "Authorization: Bearer ${SDP_TOKEN}" \
--form 'inclusion_list[]=""' \
--form 'exclusion_list[]=""' \
--form "connector=${CONNECTOR_NAME}" \
--form "edge=${EDGE_NAME}" \
--form "file=@import_apps.json"
Successful response shape:
{
"code": 0,
"data": [
{
"result": "success",
"app_id": "<APPLICATION_ID>",
"app_name": "preview-a1b2c3d4",
"err_messages": []
}
]
}
Save data[0].app_id. The pipeline needs it for enable, validation, and cleanup.
To avoid duplicate-name failures, the pipeline can check the name first:
curl --location --request POST "https://${SSW_SERVER_URL}/ztna/v1/application/check_name" \
--header "Authorization: Bearer ${SDP_TOKEN}" \
--header "Content-Type: application/json" \
--data "{\"app_names\":[\"${APP_NAME}\"]}"
Enable application
Enable the application after creation:
curl --location --request PUT "https://${SSW_SERVER_URL}/ztna/v1/application/${APP_ID}/enabled" \
--header "Authorization: Bearer ${SDP_TOKEN}" \
--header "Content-Type: application/json;charset=UTF-8" \
--data '{"enabled":true}'
To disable an application, send:
{"enabled": false}
Export an application template
Exporting an existing known-good application is the easiest way to build a template:
curl --location --request POST "https://${SSW_SERVER_URL}/ztna/v1/application/export_json" \
--header "Authorization: Bearer ${SDP_TOKEN}" \
--header "Content-Type: application/json" \
--data '{"app_names":["template-private-app"]}' \
--output app_template.json
The CI/CD pipeline can then transform the exported JSON by replacing:
app_name_listapp_data_list[].nameapp_data_list[].display_nameapp_data_list[].hostapp_data_list[].hostsapp_data_list[].portapp_data_list[].portsapp_data_list[].app_web_configapp_data_list[].group_namesapp_data_list[].site_nameapp_data_list[].edge_nameapp_data_list[].network_name
Delete application
Delete the application when the preview environment is removed:
curl --location --request DELETE "https://${SSW_SERVER_URL}/ztna/v1/application/${APP_ID}" \
--header "Authorization: Bearer ${SDP_TOKEN}"
For batch cleanup:
curl --location --request DELETE "https://${SSW_SERVER_URL}/ztna/v1/bulkDeleteApplication" \
--header "Authorization: Bearer ${SDP_TOKEN}" \
--header "Content-Type: application/json" \
--data '{"id_list":["<APP_ID_1>","<APP_ID_2>"]}'
Recommended cleanup behavior:
- Store the created
app_idas a deployment output or CI/CD artifact. - Apply a naming convention such as
preview-<service>-<sha>so orphaned apps can be identified. - Retry deletion on transient errors.
- Run a scheduled cleanup job to remove old preview applications that match the naming convention.
Optional connectivity validation
The test script downloads the latest sdpc client for the current operating system, starts a local application tunnel, then checks whether the expected local port is open.
Example:
sdpc \
-sdp_server "https://${SSW_SERVER_URL}" \
-app_ports "{\"${APP_NAME}\":[9876]}" \
-apikey "${SSW_API_KEY}" \
"${APP_NAME}"
Then verify the local port:
nc -zv 127.0.0.1 9876
This step is optional for provisioning. It is useful in automated tests because it verifies that:
- the application was created,
- the application is enabled,
- the API key can access the application,
- the connector path works,
- the target host and port are reachable.
CI/CD integration pattern
Suggested provisioning flow:
deploy preview environment
-> authenticate API key
-> generate import_apps.json from template
-> import Secure Workspace application
-> save app_id
-> enable application
-> optionally validate with sdpc
Suggested teardown flow:
load saved app_id
-> authenticate API key
-> delete Secure Workspace application
-> remove preview environment
If app_id is not available during teardown, use the preview naming convention to find the matching application first, then delete it. Avoid relying only on display name because users may change display names manually.
curl --location --request GET "https://${SSW_SERVER_URL}/ztna/v1/application/by_name?app_name=${APP_NAME}" \
--header "Authorization: Bearer ${SDP_TOKEN}"
Error handling
Recommended checks:
- Authentication response contains
data.sdp_token. - Import response has
code: 0. - Import response contains a successful
data[].result. data[].app_idis not empty.- Enable request returns success.
- Delete request returns success.
Common failures:
| Failure | Likely cause | Resolution |
|---|---|---|
Token response has no sdp_token |
Invalid API key or wrong server URL. | Verify API key, tenant, environment, and server URL. |
| Import returns no permission | API key lacks ImportApplication. |
Update API key permissions. |
| Enable returns no permission | API key lacks EnableApplication. |
Update API key permissions. |
| Delete returns no permission | API key lacks RemoveApplication. |
Update API key permissions. |
| Application already exists | The application name is already used in the organization. |
Delete the old app first or generate a unique name. |
| Connector not found | Multipart connector value does not match an existing connector in the org. |
Verify connector name and organization. |
| Edge not found | Multipart edge value does not match an existing edge. |
Verify edge name. |
| Group not found | group_names contains a group that does not exist. |
Create the group or update the payload. |
| Connectivity validation fails | Connector path or target host/port is unreachable. | Verify DNS, target service, connector, firewall, and port. |
Security recommendations
- Use a dedicated automation API key, not a personal administrator key.
- Grant only the required permissions.
- Store the API key in a CI/CD secret store.
- Do not print the API key or bearer token in logs.
- Use predictable but non-sensitive names for preview applications.
- Clean up applications when preview environments are removed.
- Periodically audit old preview applications by naming convention and creation time.
Reference implementation
See:
infrastructure/tools/auto_app_testing.sh
The script demonstrates:
- exchanging an API key for SDP/IAM tokens,
- generating an import JSON file,
- importing a Private Application,
- exporting the application for validation,
- enabling the application,
- downloading and running
sdpc, - validating local connectivity,
- deleting the application,
- removing temporary files.