--- title: "Manage Private Applications by API" slug: "manage-private-applications-by-api" updated: 2026-06-26T05:55:13Z published: 2026-06-26T05:55:13Z canonical: "docs.ssw.splashtop.com/manage-private-applications-by-api" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.ssw.splashtop.com/llms.txt > Use this file to discover all available pages before exploring further. # Manage Private Applications by API 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-.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: 1. CI/CD deploys the preview environment. 2. CI/CD creates a matching Secure Workspace Private Application. 3. CI/CD assigns the application to the expected connector, edge, network, and user groups. 4. CI/CD enables the application. 5. Optional: CI/CD validates access with the `sdpc` command-line client. 6. 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, Virginia` - `Portland, Oregon` - `Frankfurt, Germany` - `London, England` - `Tokyo, Japan` - `HongKong, China` - `Taipei, 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": "", "iam_token": "" } } ``` Use `data.sdp_token` as the bearer token for application management APIs: ``` Authorization: Bearer ``` ## 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: - `name` must be unique within the organization. The import API is not an upsert API. If an application with the same name already exists, import fails. - `connector` and `edge` in the multipart form can override the values in the JSON file. - `group_names` must 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_config` is a JSON string, not a nested JSON object. - For private applications, set `via_private_network` to `true`. ## 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": "", "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_list` - `app_data_list[].name` - `app_data_list[].display_name` - `app_data_list[].host` - `app_data_list[].hosts` - `app_data_list[].port` - `app_data_list[].ports` - `app_data_list[].app_web_config` - `app_data_list[].group_names` - `app_data_list[].site_name` - `app_data_list[].edge_name` - `app_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":["",""]}' ``` Recommended cleanup behavior: - Store the created `app_id` as 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_id` is 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.