Skip to content

Configurations

The Developer Portal reads its configuration from configs/config.toml, layered over built-in defaults (src/config/configDefaults.js). This page explains how that file is loaded, how environment values are injected through interpolation tokens, and gives the full reference of every supported key.

How Configuration Is Loaded

Precedence, lowest to highest:

  1. Built-in defaults (src/config/configDefaults.js)
  2. configs/config.toml, with any {{ env }} / {{ file }} references resolved

configs/config-template.toml documents every supported key with its default value as plain literals — a reference copy, not the file the portal actually reads.

Environment variables do not override config keys directly

There is no automatic APIP_DP_* prefix that maps environment variables onto config keys. An environment value reaches a setting only through an explicit interpolation token written into config.toml, resolved when the file loads. A field with no token always takes its literal TOML value (or the built-in default).

Interpolation Tokens

Token Behavior
{{ env "NAME" }} Substitutes the value of environment variable NAME. Fails closed (aborts startup) if NAME is unset or empty — it does not fall through to a default.
{{ env "NAME" "default" }} Substitutes NAME's value if set and non-empty, else the literal "default".
{{ file "/path" }} Reads a secret value from a mounted file at /path, trimmed. Always required — a missing, unreadable, oversized, or disallowed path is a hard startup error.

An example from the shipped config.toml:

[developer_portal.security]
encryption_key = '{{ env "APIP_DP_SECURITY_ENCRYPTION_KEY" }}'
session_secret = '{{ env "APIP_DP_SECURITY_SESSION_SECRET" }}'

Partial substitution works too — 'foo-{{ env "X" }}' resolves to "foo-bar" if X=bar.

{{ file }} path allowlist

{{ file "/path" }} only reads from /etc/devportal or /secrets/devportal by default. Override with the APIP_CONFIG_FILE_SOURCE_ALLOWLIST environment variable (comma-separated directories) — read directly from the process environment rather than through {{ env }}, since it gates interpolation itself.

Server

[developer_portal.server]
base_url = '{{ env "APIP_DP_SERVER_BASE_URL" "https://localhost:9543" }}'
port = 9543

[developer_portal.server.https]
enabled = false
cert_file = "./resources/security/client-truststore.pem"
key_file = "./resources/security/private-key.pem"
Key Default Description
server.base_url https://localhost:9543 Canonical public origin, used only to build absolute URLs embedded in generated agent prompts
server.port 9543 Single listener port
server.https.enabled false Whether the listener terminates TLS itself. Set false only when a trusted upstream (proxy/LB/ingress) terminates TLS
server.https.cert_file / key_file Required only when https.enabled = true — no self-signed fallback

Logging

[developer_portal.logging]
level = "info"          # debug | info | warn | error
format = "text"         # text | json
console_only = true     # true: stdout only. false: also write rotating log files to disk

Database

[developer_portal.database]
driver = "sqlite"             # sqlite | postgres | mssql
path = "./devportal.db"       # SQLite only
host = "localhost"            # PostgreSQL / MSSQL only
port = 5432                   # PostgreSQL / MSSQL only (1433 for MSSQL)
name = "devportal"            # PostgreSQL / MSSQL only
user = "postgres"             # PostgreSQL / MSSQL only
password = ""                 # PostgreSQL / MSSQL only
ssl_mode = "disable"          # PostgreSQL / MSSQL only: disable | verify-full
ssl_root_cert = "./resources/security/ca.pem"
max_open_conns = 50
min_open_conns = 2
pool_idle_timeout_ms = 10000
pool_connection_timeout_ms = 30000
pool_request_timeout_ms = 30000         # MSSQL only — per-query execution timeout

Pool settings are validated at startup

For postgres/mssql drivers, max_open_conns must be an integer ≥ 1, the remaining pool settings must be non-negative integers, and min_open_conns must not exceed max_open_conns. An invalid value fails startup closed with a [FATAL] message rather than silently reaching the connection pool.

Security

[developer_portal.security]
encryption_key = ""     # 64-char hex — AES-256-GCM key for encrypting secrets at rest
session_secret = ""     # 64-char hex — express-session signing secret

[developer_portal.security.service_api_key]
enabled = true
header_name = "x-wso2-api-key"
value = ""

encryption_key and session_secret are required — the portal fails closed at startup if either doesn't resolve to a 64-character hex string. Generate one with openssl rand -hex 32.

Authentication

[developer_portal.auth]
mode = "local"          # local | idp
role_validation = false # Enforce per-operation role validation

[developer_portal.auth.claim_mappings]
organization = "org_name"
roles = "roles"
groups = "groups"

[developer_portal.auth.local]
platform_api_url = ""
public_key_path = ""
tls_skip_verify = false

[developer_portal.auth.idp]
name = "IS"
issuer = "https://localhost:9443/oauth2/token"
authorization_url = "https://localhost:9443/oauth2/authorize"
token_url = "https://localhost:9443/oauth2/token"
user_info_url = "https://localhost:9443/oauth2/userinfo"
client_id = ""
client_secret = ""
audience = ""
callback_url = "http://localhost:9543/default/callback"
scope = "openid profile email"
logout_url = "https://localhost:9443/oidc/logout"
logout_redirect_uri = "http://localhost:9543/default"
certificate = ""
jwks_url = "https://localhost:9443/oauth2/jwks"
token_refresh_timeout_ms = 10000

[developer_portal.auth.idp.roles]
admin = "admin"
subscriber = "Internal/subscriber"
super_admin = "superAdmin"

See Integrate Third-Party Identity Providers for the full field-by-field reference and Asgardeo/Keycloak walkthroughs.

Page Access Rules

Additions only — the portal always protects its own pages (applications, API keys, subscriptions, settings) regardless of what's listed here. Use this to require login/authorization for a custom page you've added:

# [developer_portal.page_access_rules]
# authenticated = ["**/my-custom-page"]
# authorized = ["**/my-custom-page"]

Patterns are glob-matched (minimatch) against the request URL and merged with — never replace — the built-in list.

Organization

[developer_portal.organization]
default_name = "default"                 # "" disables auto-seeding
auto_create_subscription_plans = true    # Auto-create Bronze/Silver/Gold/Unlimited/AsyncUnlimited

Bootstraps a default organization on startup — idempotent, safe to leave enabled across restarts.

Design Mode

# [developer_portal.design_mode]
# enabled = false
# path_to_layout = "./src/defaultContent/"
# api_samples_path = "./samples/apis/"
# mcp_samples_path = "./samples/mcps/"
# subscription_plans_path = "./samples/subscription-plans.yaml"
# applications_path = "./samples/applications.yaml"

Disabled by default. See Design Mode for the full field reference and theme-development workflow.

Webhooks

[developer_portal.webhooks.delivery]
poll_interval_ms = 2000
batch_size = 50
signature_tolerance_sec = 300

Global delivery tuning only — subscribers themselves are per-organization, managed via the Webhook Integration settings tab, not this file. Each delivery is attempted exactly once; there's no retry/backoff.