Bottom-up API deployment¶
This guide explains how to deploy a REST API directly to a Platform Gateway and have it automatically synced to an on-premises WSO2 API Manager 4.7.x Control Plane.
Overview¶
When you deploy a REST API through the gateway controller's management REST API, the gateway automatically tracks it for bottom-up sync to an on-premises WSO2 API Manager 4.7.x instance, if the gateway is configured for on-premises Control Plane mode.
Understand deployment approaches¶
Platform Gateway supports two deployment approaches, distinguished by the direction the API definition flows:
Top-down deployment (Control Plane → Gateway)
In top-down deployment, the Control Plane is the source of truth. You create and deploy the API from the Publisher Portal, and the Control Plane pushes the API definition to the gateway. For this flow, see Getting started with Platform Gateway.
Bottom-up deployment (Gateway → on-premises API Manager)
In bottom-up deployment, you deploy a REST API directly to the gateway through its POST /rest-apis management endpoint. The gateway makes the API available immediately, then syncs it to the on-premises Control Plane in the background. This mode is only available when the Control Plane is running in on-premises mode — the cloud Control Plane does not support bottom-up sync.
| Aspect | Top-down | Bottom-up |
|---|---|---|
| Initiation | Control Plane | Gateway (REST API) |
| Direction | Control Plane → Gateway | Gateway → on-premises API Manager |
| Source of truth | Central Control Plane | Local gateway |
| Status tracking | Managed by the Control Plane | Tracked in the gateway database |
| Failure handling | Depends on the Control Plane | API keeps working; sync retries automatically |
This guide focuses on bottom-up deployment.
Prerequisites¶
Before you begin, ensure that you have the following:
- A running gateway controller, with its management API endpoint accessible (default:
http://localhost:9090/api/management/v1). - An on-premises WSO2 API Manager 4.7.x instance, reachable from the gateway controller.
- API Manager OAuth2 credentials for the gateway to authenticate with (either a client ID and secret, or a username and password).
Configure the gateway controller¶
Add a [controller.controlplane] section to the gateway controller's config.toml file to enable bottom-up sync to the on-premises API Manager instance:
[controller.server]
gateway_id = "gateway-1"
port = 9090
[controller.controlplane]
# For bottom-up sync to on-prem APIM
host = "192.168.0.102:9443"
insecure_skip_verify = true
# OAuth2 credentials (choose one option)
# Option A: Client credentials flow
apim_oauth2_client_id = "<clientId>"
apim_oauth2_client_secret = "<clientSecret>"
# Option B: Resource owner password flow
# apim_oauth2_username = "admin"
# apim_oauth2_password = "admin"
gateway_name = "onprem-gw"
[controller.auth.basic]
enabled = true
[[controller.auth.basic.users]]
username = '{{ env "APIP_GW_CONTROLLER_AUTH_BASIC_ADMIN_USERNAME" "" }}'
password = '{{ env "APIP_GW_CONTROLLER_AUTH_BASIC_ADMIN_PASSWORD_HASH" "" }}'
password_hashed = true
roles = ["admin"]
How it works¶
Sync behavior¶
When you deploy a REST API through the gateway controller:
- The API is deployed to the gateway immediately, and is available for routing, API key creation, and request processing.
- If
controller.controlplane.hostis configured and the gateway is connected to an on-premises API Manager instance, the gateway syncs the API to the Control Plane. - The sync status is tracked in the gateway database as
pending,success, orfailed, with up to three automatic retries on failure.
Conditions for sync¶
The gateway only attempts a sync when all of the following are true:
- An API Manager Control Plane connection is configured.
- The gateway's WebSocket connection to the Control Plane is active.
- The Control Plane is detected as running in on-premises mode (through its
.well-knownendpoint).
If sync fails¶
- The API remains available on the gateway regardless of sync status.
- The sync status is marked
failed, with error details recorded. - The gateway retries automatically on the next API update.
- You can also trigger a manual retry by updating the API with its current definition.
Deploy a REST API¶
Bottom-up APIs are REST APIs deployed directly to the gateway controller. Once deployed, they're tracked for automatic sync to the on-premises API Manager Control Plane — no manual push is required, and the API keeps working even if the Control Plane is temporarily unavailable.
API definition¶
The following example defines a REST API with API-level and operation-level policies:
apiVersion: gateway.api-platform.wso2.com/v1
kind: RestApi
metadata:
name: PetStoreAPI
spec:
displayName: PetStore API
version: v1.0
context: /petstore
upstream:
main:
url: "https://petstore.example.com"
# API-level policies apply to all operations
policies:
- name: api-key-auth
version: v1
params:
key: X-API-Key
in: header
- name: basic-ratelimit
version: v1
params:
limits:
- requests: 1000
duration: 1h
operations:
- method: GET
path: /pet/{petId}
# Operation-level policies override API-level policies
policies:
- name: basic-ratelimit
version: v1
params:
limits:
- requests: 100
duration: 1m
- method: POST
path: /pet
- method: PUT
path: /pet
- method: DELETE
path: /pet/{petId}
- method: GET
path: /store/inventory
- method: POST
path: /store/order
- method: GET
path: /
Keep the following in mind when defining a bottom-up API:
metadata.namemust be unique per API.contextbecomes the URL path prefix on the gateway.upstream.main.urlis required and points to the backend service.versionis tracked separately, so you can run multiple versions of the same API.- Policies are optional, but recommended for securing the API.
Deployment steps¶
Step 1: Create the API definition
Save the API definition shown above as petstore-api.yaml.
Step 2: Deploy the API to the gateway
The following commands use a BASE64_CREDENTIALS environment variable for the Basic auth header. Set ADMIN_PASSWORD to the password printed by the gateway's scripts/setup.sh script (the default username is admin):
export ADMIN_USERNAME=admin
export ADMIN_PASSWORD='<the password scripts/setup.sh printed>'
export BASE64_CREDENTIALS=$(echo -n "$ADMIN_USERNAME:$ADMIN_PASSWORD" | base64)
curl -X POST http://localhost:9090/api/management/v1/rest-apis \
-H "Content-Type: application/yaml" \
-H "Authorization: Basic ${BASE64_CREDENTIALS}" \
--data-binary @petstore-api.yaml
A successful response looks like this:
{
"uuid": "api-uuid-12345",
"displayName": "PetStore API",
"version": "v1.0",
"context": "/petstore",
"origin": "gateway_api",
"desiredState": "deployed",
"createdAt": "2026-04-26T10:30:00Z"
}
Note
To confirm the Control Plane sync status, check the gateway controller logs for Bottom-up sync: starting and Bottom-up sync: API synced successfully entries. If the sync fails, the log includes the error detail, and the gateway retries automatically.
Step 3: Test the API on the gateway
# Get an API key
curl -X POST http://localhost:9090/api/management/v1/rest-apis/PetStoreAPI/api-keys \
-H "Content-Type: application/json" \
-H "Authorization: Basic ${BASE64_CREDENTIALS}" \
-d '{"name": "test-key"}'
# Invoke the API
curl http://localhost:8080/petstore/pet/123 \
-H "X-API-Key: <api-key-from-step-above>"
Step 4: Verify the API in the on-premises API Manager (optional)
Once the sync completes, the API is available in API Manager:
curl -X GET https://192.168.0.102:9443/api/am/publisher/v4/apis \
-H "Authorization: Bearer <apim-token>" | jq '.list[] | select(.name=="PetStore API")'
Update an API¶
When you update an API, the gateway automatically re-syncs it to the on-premises API Manager instance, if connected.
For example, to add a rate-limiting policy, update the API definition and save it as petstore-api-updated.yaml:
apiVersion: gateway.api-platform.wso2.com/v1
kind: RestApi
metadata:
name: PetStoreAPI
spec:
displayName: PetStore API
version: v1.0
context: /petstore
upstream:
main:
url: https://petstore.example.com
policies:
- name: basic-ratelimit
version: v1
params:
limits:
- requests: 1000
duration: 1h
- name: api-key-auth
version: v1
params:
key: X-API-Key
in: header
operations:
- method: GET
path: /pet/{petId}
Send the update:
curl -X PUT http://localhost:9090/api/management/v1/rest-apis/PetStoreAPI \
-H "Content-Type: application/yaml" \
-H "Authorization: Basic ${BASE64_CREDENTIALS}" \
--data-binary @petstore-api-updated.yaml
When you update an API:
- The gateway applies the update immediately.
- The sync status resets to
pending. - The gateway automatically re-syncs the API to the on-premises API Manager instance.
- Once sync completes, the gateway and API Manager both reflect the updated version.
To monitor the sync result, watch the gateway controller logs:
./gateway-controller 2>&1 | grep "Bottom-up sync"
Undeploy an API¶
To remove an API from both the gateway and the on-premises API Manager instance, send a delete request with the API name:
curl -X DELETE http://localhost:9090/api/management/v1/rest-apis/PetStoreAPI \
-H "Authorization: Basic ${BASE64_CREDENTIALS}"
A successful response looks like this:
{
"status": "success",
"message": "RestAPI deleted successfully",
"id": "PetStoreAPI"
}
Manage API keys¶
Create an API key
curl -X POST http://localhost:9090/api/management/v1/rest-apis/PetStoreAPI/api-keys \
-H "Content-Type: application/json" \
-H "Authorization: Basic ${BASE64_CREDENTIALS}" \
-d '{
"name": "my-api-key"
}'
Invoke the API with the key
curl http://localhost:8080/petstore/pet/1 \
-H "X-API-Key: <your-api-key>"
List API keys
curl -X GET http://localhost:9090/api/management/v1/rest-apis/PetStoreAPI/api-keys \
-H "Authorization: Basic ${BASE64_CREDENTIALS}"
Revoke an API key
curl -X DELETE http://localhost:9090/api/management/v1/rest-apis/PetStoreAPI/api-keys/<key-uuid> \
-H "Authorization: Basic ${BASE64_CREDENTIALS}"
Track sync status¶
The gateway tracks the API Manager sync state internally, and you can monitor it through the gateway controller logs.
| Status | Meaning | Log signal |
|---|---|---|
pending |
Waiting to sync to API Manager | Bottom-up sync: starting |
success |
Successfully synced to API Manager | Bottom-up sync: API synced successfully |
failed |
Sync failed after three retries | Bottom-up sync: all retries exhausted |
If a sync fails, the gateway retries automatically up to three times per sync cycle, and again on the next Control Plane reconnection. To trigger a retry manually, update the API with its current definition:
curl -X PUT http://localhost:9090/api/management/v1/rest-apis/PetStoreAPI \
-H "Content-Type: application/yaml" \
-H "Authorization: Basic ${BASE64_CREDENTIALS}" \
--data-binary @petstore-api.yaml
Troubleshooting¶
API not syncing to API Manager¶
Symptom: The gateway logs show Bottom-up sync: attempt failed or Bottom-up sync: all retries exhausted.
Control Plane not configured — Check whether the Control Plane host is set:
echo $APIP_GW_CONTROLLER_CONTROLPLANE_HOST
# or check the [controller.controlplane] section in config.toml
Set the on-premises API Manager host and OAuth2 credentials. These are read through the environment interpolation tokens already present in the shipped config.toml:
export APIP_GW_CONTROLLER_CONTROLPLANE_HOST=192.168.0.102:9443
export APIP_GW_CONTROLLER_CONTROLPLANE_APIM_OAUTH2_CLIENT_ID=...
export APIP_GW_CONTROLLER_CONTROLPLANE_APIM_OAUTH2_CLIENT_SECRET=...
API Manager not reachable — Test the Control Plane's .well-known endpoint:
curl -k https://192.168.0.102:9443/internal/gateway/.well-known
# Expected: {"gatewayPath": "/internal/data/v1"}
If this fails, verify network connectivity and firewall rules to the API Manager host, and check whether insecure_skip_verify is needed for your TLS setup.
Invalid OAuth2 credentials — Check the API Manager logs for authentication errors. Verify that apim_oauth2_client_id and apim_oauth2_client_secret are correct and have the apim:api_import_export and apim:api_view scopes. If needed, try the username and password flow instead of client credentials.
Missing upstream URL — Check the API definition:
curl -X GET http://localhost:9090/api/management/v1/rest-apis/PetStoreAPI \
-H "Authorization: Basic ${BASE64_CREDENTIALS}" | jq '.spec.upstream'
Ensure upstream.main.url is set to a valid backend service.
Gateway controller not connected to API Manager — Enable debug logging and check for connection events:
export APIP_GW_CONTROLLER_LOGGING_LEVEL=debug
Restart the gateway controller and look for these log entries:
Connected to control plane— connected to API Manager.Failed to resolve gateway path— can't reach API Manager.Resolved WebSocket connect URL— on-premises mode detected.
If the gateway isn't connected, wait for the periodic automatic reconnection, or trigger a sync immediately by updating the API with its current definition.
"Access token not found"¶
This means the API Manager OAuth2 token endpoint returned an unexpected response. Verify your credentials are correct, confirm the OAuth2 token endpoint is accessible, and enable debug logging (APIP_GW_CONTROLLER_LOGGING_LEVEL=debug) to inspect the actual response.
"On-prem Control Plane mode not enabled"¶
This means the gateway couldn't detect that the Control Plane is running in on-premises mode. Verify that the API Manager .well-known endpoint returns a gatewayPath, check network connectivity, and confirm the Control Plane host is configured correctly:
curl -k https://192.168.0.102:9443/internal/gateway/.well-known
Expected response:
{
"gatewayPath": "/internal/data/v1"
}