platform-api

module
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: Apache-2.0

README

Platform API

Backend service that powers the API Platform portals, gateways, and automation flows.

Quick Start

Prerequisites

Before using the Platform API, obtain a bearer token for authentication. In local JWT mode (default) you can generate a token using the configured APIP_CP_AUTH_JWT_SECRET_KEY. In IDP mode, obtain a token from your identity provider.

Build and Run
# Build
cd platform-api
go build ./cmd/main.go

# Run (TLS with self-signed certificates)
cd platform-api
go run ./cmd/main.go
Database Configuration

Platform API supports sqlite3 (default), postgres, and sqlserver.

# SQL Server example
export DATABASE_DRIVER=sqlserver
export DATABASE_HOST=sqlserver.example.internal
export DATABASE_PORT=1433
export DATABASE_NAME=platform_api
export DATABASE_USER=sa
export DATABASE_PASSWORD='<strong-password>'
export DATABASE_SSL_MODE=disable

cd platform-api
go run ./cmd/main.go
Step-by-Step Workflow

Across the API, resources with a handle expose it as id (an immutable, URL-safe slug), with a separate human-readable displayName. Path parameters are handle-based, not UUIDs — e.g. {projectId}, {gatewayId}, {restApiId} are all handles. See src/resources/openapi.yaml for the full contract.

1. Register an Organization

curl -k -X POST https://localhost:9243/api/v0.9/organizations \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <your-token>' \
  -d '{"id":"acme","displayName":"ACME Corporation","region":"us-east-1"}'

2. Create a Project

curl -k -X POST https://localhost:9243/api/v0.9/projects \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <your-token>' \
  -d '{
    "displayName": "Production APIs"
  }'

Response includes the project handle, auto-generated from displayName if id is omitted:

{
  "id": "production-apis",
  "displayName": "Production APIs",
  "organizationId": "acme",
  "createdAt": "2026-06-21T15:12:44+05:30",
  "updatedAt": "2026-06-21T15:12:44+05:30"
}

3. Create a Gateway

curl -k -X POST https://localhost:9243/api/v0.9/gateways \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer <your-token>' \
  -d '{
    "id": "prod-gateway-01",
    "displayName": "Production Gateway 01",
    "endpoints": ["https://prod-gateway-01.example.com:8443/api/v1"],
    "functionalityType": "regular"
  }'

Response includes the gateway handle (used as {gatewayId} in all subsequent calls):

{
  "id": "prod-gateway-01",
  "displayName": "Production Gateway 01",
  "organizationId": "acme",
  "endpoints": ["https://prod-gateway-01.example.com:8443/api/v1"],
  "functionalityType": "regular",
  "isCritical": false,
  "version": "1.0",
  "isActive": false,
  "createdAt": "2026-06-21T15:12:44+05:30",
  "updatedAt": "2026-06-21T15:12:44+05:30"
}

4. Generate Gateway Token

curl -k -X POST https://localhost:9243/api/v0.9/gateways/prod-gateway-01/tokens \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer <your-token>'

Response includes the gateway authentication token:

{
  "id": "7ed55286-66a4-43ae-9271-bd1ead475a55",
  "token": "QY8Rnm9bJ-incsGU0xtFz2vx16I1IVhEf0Ma_4O5F9s",
  "createdAt": "2026-06-21T15:12:57+05:30",
  "message": "New token generated successfully. Old token remains active until revoked."
}

List Gateway Tokens:

curl -k https://localhost:9243/api/v0.9/gateways/prod-gateway-01/tokens \
  -H 'Authorization: Bearer <your-token>'

Returns a bare array of token summaries ([{"id": "...", "status": "active", "createdAt": "...", "revokedAt": null}]) — token hashes are never exposed.

5. Connect Gateway to Platform (WebSocket)

Install wscat if not already installed:

npm install -g wscat

Connect using the gateway token:

wscat -n -c wss://localhost:9243/api/internal/v1/ws/gateways/connect \
  -H "api-key: <gateway-token>"

Expected output:

Connected (press CTRL+C to quit)
< {"type":"connection.ack","gatewayId":"4dac93bd-07ba-417e-aef8-353cebe3ba73","connectionId":"3150a8b6-649d-4d12-8512-7d72e8ec7f13","timestamp":"2026-06-21T14:42:13+05:30"}

Note: gatewayId on WebSocket events is the gateway's internal UUID, not the handle returned by the REST API — the gateway itself doesn't need to know its handle.

Keep this connection open to receive real-time deployment events.

6. Create an API

curl -k -X POST 'https://localhost:9243/api/v0.9/rest-apis' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <your-token>' \
  -d '{
      "id": "weather-api",
      "displayName": "Weather API",
      "description": "Weather API with main and sandbox upstreams",
      "context": "weather",
      "version": "1.0.0",
      "projectId": "production-apis",
      "lifeCycleStatus": "CREATED",
      "transport": ["http","https"],
      "upstream": {
         "main": { "url": "http://sample-backend:5000" },
         "sandbox": { "url": "http://sample-backend:5000/sandbox" }
       }
    }'

projectId is the project's handle (from step 2), not its UUID.

7. Deploy API to Gateway

curl -k -X POST 'https://localhost:9243/api/v0.9/rest-apis/weather-api/deployments' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer <your-token>' \
  -d '{
    "name": "weather-v1-prod",
    "base": "current",
    "gatewayId": "prod-gateway-01",
    "metadata": {
      "vhostMain": "example.wso2.com",
      "vhostSandbox": "sand-example.wso2.com"
    }
}'

gatewayId is the gateway's handle (from step 3), not its UUID.

Expected response:

{
  "deploymentId": "90d10e1c-8560-5c36-9d5a-124ecaa17485",
  "name": "weather-v1-prod",
  "gatewayId": "prod-gateway-01",
  "status": "DEPLOYED",
  "metadata": {
    "vhostMain": "example.wso2.com",
    "vhostSandbox": "sand-example.wso2.com"
  },
  "createdAt": "2026-06-21T16:15:18+05:30",
  "updatedAt": "2026-06-21T16:15:18+05:30",
  "baseDeploymentId": null
}

The connected gateway will receive a deployment event via WebSocket:

< {"type":"api.deployed","payload":{"apiId":"54588845-c860-4a56-8802-c06b03028543","deploymentId":"90d10e1c-8560-5c36-9d5a-124ecaa17485","performedAt":"2026-06-21T16:15:18+05:30"},"gatewayId":"4dac93bd-07ba-417e-aef8-353cebe3ba73","timestamp":"2026-06-21T16:15:18+05:30","correlationId":"ae7488ec-9559-4a81-bddd-b85e1391d2c0"}

apiId and gatewayId in the event payload are internal UUIDs, distinct from the handle-based id used in the REST API.

Configuration

Configuration is read from a TOML config file (-config <path>), layered over built-in defaults. Environment variables do not override config keys directly. The only way an environment variable affects a setting is through an explicit {{ env "NAME" }} interpolation token placed in the config file, which is resolved at load time via os.LookupEnv; a field with no token always takes its literal TOML value (or the built-in default). See "Providing secrets via the config file" below.

One variable is intentionally not prefixed: the shared APIP_CONFIG_FILE_SOURCE_ALLOWLIST. The {{ env "NAME" }} interpolation tokens in the config file read the literal name via os.LookupEnv (independent of the koanf prefix mechanism); the samples use the same APIP_CP_-prefixed names for one consistent namespace — e.g. {{ env "APIP_CP_ENCRYPTION_KEY" }} (see "Providing secrets via the config file" below).

Authentication

Two authentication modes are supported. Exactly one should be active at a time.

APIP_CP_AUTH_IDP_ENABLED=false (default)  →  Local JWT mode  (HMAC signature verification)
APIP_CP_AUTH_IDP_ENABLED=true             →  IDP mode        (JWKS-based verification)

APIP_CP_ENCRYPTION_KEY and APIP_CP_AUTH_JWT_SECRET_KEY are required; startup fails without them. TLS certificates are likewise required whenever the HTTPS listener is enabled — the server never generates a self-signed pair.


Local JWT Mode (default)

The server signs and validates HMAC login tokens using the key that auth.jwt.secret_key resolves to — a 32-byte value (64 hex chars or base64). This key is required at startup whenever local JWT or file-based auth is enabled and is never generated; a missing or malformed value fails startup. The sample config reads it from {{ env "APIP_CP_AUTH_JWT_SECRET_KEY" }}.

auth.jwt.skip_validation is a development-only switch that accepts bearer values without a signature check. It is honored only in demo mode — startup is rejected if it is true while APIP_DEMO_MODE=false — and it does not remove the required secret key above.

Variable Default Description
APIP_CP_AUTH_JWT_SECRET_KEY (empty) HMAC key for signing/verifying login JWTs — 32-byte value (64 hex or base64; openssl rand -hex 32). Required whenever local JWT or file-based auth is enabled.
APIP_CP_AUTH_JWT_ISSUER platform-api Expected iss claim value
APIP_CP_AUTH_JWT_SKIP_VALIDATION false Skip signature verification — development only, honored solely in demo mode

Run locally. The config file supplies the {{ env }} tokens, so export the referenced variable and pass -config:

export APIP_CP_AUTH_JWT_SECRET_KEY="$(openssl rand -hex 32)"
go run ./cmd/main.go -config config/config.toml

To skip signature checks during local development, set skip_validation = true under [auth.jwt] in the config file (demo mode only) and run the same command.

Legacy variable names. These unprefixed names are no longer read — environment variables affect configuration only through {{ env "…" }} tokens (see above). Use the current token variable name (or the config key directly) instead:

Old name New name
JWT_SECRET_KEY APIP_CP_AUTH_JWT_SECRET_KEY
JWT_ISSUER APIP_CP_AUTH_JWT_ISSUER
JWT_SKIP_VALIDATION APIP_CP_AUTH_JWT_SKIP_VALIDATION
JWT_SKIP_PATHS APIP_CP_AUTH_SKIP_PATHS

IDP Mode

Tokens are validated against any standards-compliant identity provider (Thunder, Asgardeo, Keycloak, Azure AD, Okta, etc.) using its JWKS endpoint. Set APIP_CP_AUTH_IDP_ENABLED=true and supply at minimum APIP_CP_AUTH_IDP_JWKS_URL and APIP_CP_AUTH_IDP_ISSUER.

Variable Default Description
APIP_CP_AUTH_IDP_ENABLED false Set to true to activate IDP mode
APIP_CP_AUTH_IDP_NAME (empty) Optional label shown in startup logs (e.g. thunder, asgardeo)
APIP_CP_AUTH_IDP_JWKS_URL (required) IDP's JWKS endpoint for public key retrieval
APIP_CP_AUTH_IDP_ISSUER (required) Accepted JWT issuer
APIP_CP_AUTH_IDP_AUDIENCE (empty) Accepted JWT audience. When set, the token's aud claim must contain this value; empty skips the check
APIP_CP_AUTH_IDP_CLAIM_MAPPINGS_ORGANIZATION_CLAIM_NAME organization JWT claim holding the org UUID for the active session
APIP_CP_AUTH_IDP_CLAIM_MAPPINGS_ORG_NAME_CLAIM_NAME org_name JWT claim for the org display name
APIP_CP_AUTH_IDP_CLAIM_MAPPINGS_ORG_HANDLE_CLAIM_NAME org_handle JWT claim for the org URL-safe handle
APIP_CP_AUTH_IDP_CLAIM_MAPPINGS_USER_ID_CLAIM_NAME sub JWT claim used as the canonical user identifier
APIP_CP_AUTH_IDP_CLAIM_MAPPINGS_USERNAME_CLAIM_NAME username JWT claim for the human-readable username
APIP_CP_AUTH_IDP_CLAIM_MAPPINGS_EMAIL_CLAIM_NAME email JWT claim for the user's email address
APIP_CP_AUTH_IDP_CLAIM_MAPPINGS_SCOPE_CLAIM_NAME scope JWT claim carrying granted OAuth2 scopes
APIP_CP_AUTH_IDP_VALIDATION_MODE scope Authorization mode: scope (validate scope claim directly) or role (expand IDP roles to platform roles)
APIP_CP_AUTH_IDP_CLAIM_MAPPINGS_ROLES_CLAIM_PATH (empty) Dot-notation path to the roles claim (e.g. realm_access.roles). Required when APIP_CP_AUTH_IDP_VALIDATION_MODE=role
APIP_CP_AUTH_IDP_ROLE_MAPPINGS (empty) Comma-separated idp-role=platform-role pairs (e.g. PLATFORM_ADMIN=admin,PLATFORM_DEV=developer). When empty, IDP role values are used as-is

Example — Asgardeo:

export APIP_CP_AUTH_IDP_ENABLED=true
export APIP_CP_AUTH_IDP_NAME=asgardeo
export APIP_CP_AUTH_IDP_JWKS_URL=https://api.asgardeo.io/t/<org>/oauth2/jwks
export APIP_CP_AUTH_IDP_ISSUER=https://api.asgardeo.io/t/<org>/oauth2/token
export APIP_CP_AUTH_IDP_AUDIENCE=<client-id>
export APIP_CP_AUTH_IDP_ORGANIZATION_CLAIM_NAME=organizationId
export APIP_CP_AUTH_IDP_VALIDATION_MODE=scope
export APIP_CP_AUTH_IDP_ROLES_CLAIM_PATH=scope

Skip Paths

Path prefixes listed here bypass authentication entirely. Used for internal gateway traffic and health checks.

Variable Default
APIP_CP_AUTH_SKIP_PATHS /health,/metrics,/api/internal/v1/ws/gateways/connect,...

To extend the default list:

export APIP_CP_AUTH_SKIP_PATHS="/health,/metrics,/api/internal/v1/ws/gateways/connect,/my-custom-path"

Role-Based Access Control (RBAC)

Per-route scope checks are enforced when APIP_CP_ENABLE_SCOPE_VALIDATION=true. Five built-in platform roles exist:

Role Persona Access level
admin Platform administrator Full access to all resources and operations
developer API designer Full API lifecycle; cannot manage gateways or subscription plans
publisher DevPortal manager Read APIs and publish/unpublish to DevPortals; cannot create or deploy
operator CI/CD service account Deploy and undeploy operations only; cannot create resources or manage credentials
viewer Auditor Read-only access to all resources
Variable Default Description
APIP_CP_ENABLE_SCOPE_VALIDATION false Set to true to enforce per-route scope/role checks

In local JWT mode, scopes are read directly from the scope claim in the token.
In IDP mode with APIP_CP_AUTH_IDP_VALIDATION_MODE=scope, scopes are read from the claim named by APIP_CP_AUTH_IDP_SCOPE_CLAIM_NAME.
In IDP mode with APIP_CP_AUTH_IDP_VALIDATION_MODE=role, IDP roles are resolved from APIP_CP_AUTH_IDP_ROLES_CLAIM_PATH, mapped via APIP_CP_AUTH_IDP_ROLE_MAPPINGS, and matched against the required roles for each route.


Database
Variable Default Description
APIP_CP_DATABASE_DRIVER sqlite3 sqlite3 or postgres
APIP_CP_DATABASE_DB_PATH ./data/api_platform.db SQLite file path (ignored for Postgres)
APIP_CP_DATABASE_HOST localhost Postgres host
APIP_CP_DATABASE_PORT 5432 Postgres port
APIP_CP_DATABASE_NAME platform_api Postgres database name
APIP_CP_DATABASE_USER (empty) Postgres username
APIP_CP_DATABASE_PASSWORD (empty) Postgres password
APIP_CP_DATABASE_SSL_MODE disable Postgres SSL mode (disable, require, verify-full)
APIP_CP_DATABASE_EXECUTE_SCHEMA_DDL true Set to false when the DB user lacks DDL privileges

Encryption

APIP_CP_ENCRYPTION_KEY protects all at-rest encryption (secrets, subscription tokens, WebSub HMAC secrets). It is never auto-generated — the operator must provide it.

Variable Default Description
APIP_CP_ENCRYPTION_KEY (empty) Required. 32-byte AES-256 key as 64 hex chars or base64 (32 bytes). Generate with openssl rand -hex 32. Startup fails if missing or malformed.
Providing secrets via the config file (preferred over raw values)

When the Platform API is configured from a TOML file, do not write raw key values into it and do not hardcode them as literal env vars in a compose file. Reference each secret (APIP_CP_ENCRYPTION_KEY, APIP_CP_AUTH_JWT_SECRET_KEY, APIP_CP_DATABASE_PASSWORD, APIP_CP_WEBHOOK_SECRET, …) with an interpolation token that is resolved at startup, preferring a mounted file:

encryption_key = '{{ env "APIP_CP_ENCRYPTION_KEY" }}'            # from an env var
# preferred — from a mounted secret file:
# encryption_key = '{{ file "/secrets/platform-api/encryption_key" }}'

For the {{ env }} form, supply the value from a git-ignored env file rather than the shell or the compose file — the samples keep secrets in keys.env and start the stack with docker compose --env-file keys.env up, which the compose forwards into the container via an environment: ${APIP_CP_…} passthrough (never an env_file: block or a hardcoded value).

{{ file }} reads are restricted to an allowlist (/etc/platform-api, /secrets/platform-api; override with the shared APIP_CONFIG_FILE_SOURCE_ALLOWLIST env var). Resolution fails closed: a missing/empty required env var, or a missing/disallowed/oversize file, aborts startup.


Other Settings
Variable Default Description
LOG_LEVEL DEBUG Log verbosity (DEBUG, INFO, WARN, ERROR)
HTTPS_ENABLED true Enable the TLS listener. Certificates are read from HTTPS_CERT_DIR (cert.pem / key.pem — required)
HTTPS_PORT 9243 Port for the TLS listener
HTTPS_CERT_DIR ./data/certs Directory holding cert.pem / key.pem (used only when HTTPS_ENABLED=true)
HTTP_ENABLED false Enable the plain-HTTP listener. Use only behind a TLS-terminating ingress/sidecar or for internal traffic — never expose directly to untrusted networks
HTTP_PORT 9080 Port for the plain-HTTP listener
TIMEOUTS_READ_HEADER 10s Max time to read request headers, on both listeners (0 disables)
TIMEOUTS_READ 60s Max time to read the whole request, including the body (0 disables)
TIMEOUTS_WRITE 120s Max time for handler execution plus response write (0 disables)
TIMEOUTS_IDLE 120s Max time a keep-alive connection may sit unused (0 disables)
DEPLOYMENTS_MAX_PER_API_GATEWAY 20 Maximum deployments per API per gateway
DEPLOYMENTS_TRANSITIONAL_STATUS_ENABLED false Show DEPLOYING/UNDEPLOYING status before gateway ack
ARTIFACT_LIMITS_MAX_LLM_PROVIDERS_PER_ORG unlimited Max LLM providers per organization (0 or unset = unlimited)
ARTIFACT_LIMITS_MAX_LLM_PROXIES_PER_ORG unlimited Max LLM proxies per organization (0 or unset = unlimited)
ARTIFACT_LIMITS_MAX_MCP_PROXIES_PER_ORG unlimited Max MCP proxies per organization (0 or unset = unlimited)
ARTIFACT_LIMITS_MAX_WEBSUB_APIS_PER_ORG unlimited Max WebSub APIs per organization (0 or unset = unlimited)
ARTIFACT_LIMITS_MAX_WEBBROKER_APIS_PER_ORG unlimited Max WebBroker APIs per organization (0 or unset = unlimited)
GATEWAY_ENABLE_VERSION_VERIFICATION false Reject gateway connections with mismatched versions
API_KEY_HASHING_ALGORITHMS sha256 Comma-separated hash algorithms for API key storage

The legacy PORT, TLS_ENABLED, and TLS_CERT_DIR env vars are still honored and map onto the HTTPS listener (HTTPS_PORT, HTTPS_ENABLED, HTTPS_CERT_DIR).

Documentation

See spec/ for product, architecture, design, and implementation documentation.

Directories

Path Synopsis
Package api provides primitives to interact with the openapi HTTP API.
Package api provides primitives to interact with the openapi HTTP API.
Package main — OSS build: no experimental plugins registered.
Package main — OSS build: no experimental plugins registered.
internal
apperror
Package apperror defines the typed application error that handlers, services, and repositories return instead of writing HTTP error responses inline.
Package apperror defines the typed application error that handlers, services, and repositories return instead of writing HTTP error responses inline.
dto
gatewaytranslator
Package gatewaytranslator adapts a deployment artifact between two version axes:
Package gatewaytranslator adapts a deployment artifact between two version axes:
gatewaytranslator/normalizer
Package normalizer up-converts a deployment artifact from whatever platform data version it was generated from up to the gateway-latest shape, so the rest of the translator (and any subsequent down-conversion) always starts from a canonical, fully-split artifact.
Package normalizer up-converts a deployment artifact from whatever platform data version it was generated from up to the gateway-latest shape, so the rest of the translator (and any subsequent down-conversion) always starts from a canonical, fully-split artifact.
gatewaytranslator/versiontranslator
Package versiontranslator routes a gateway-latest-shaped deployment artifact to the sub-package that knows how to produce the shape a given target gateway data version expects.
Package versiontranslator routes a gateway-latest-shaped deployment artifact to the sub-package that knows how to produce the shape a given target gateway data version expects.
gatewaytranslator/versiontranslator/v1alpha1
Package v1alpha1 produces deployment artifacts for gateways older than gatewaytranslator.MinGatewayV1Version — gateways whose CRD apiVersion is "gateway.api-platform.wso2.com/v1alpha1".
Package v1alpha1 produces deployment artifacts for gateways older than gatewaytranslator.MinGatewayV1Version — gateways whose CRD apiVersion is "gateway.api-platform.wso2.com/v1alpha1".
pagination
Package pagination holds the in-memory windowing primitive shared by the handler and service layers, so the two cannot drift apart.
Package pagination holds the in-memory windowing primitive shared by the handler and service layers, so the two cannot drift apart.
plugin
Package plugin defines the compile-time pluggable module system for platform-api.
Package plugin defines the compile-time pluggable module system for platform-api.
webhook
Package webhook implements the Platform API control-plane webhook receiver.
Package webhook implements the Platform API control-plane webhook receiver.
plugins

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL