From 39f34de6c7b1d7a5e24c0a8557a1a3b27fda09e0 Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake Date: Thu, 6 Aug 2026 11:52:20 +0530 Subject: [PATCH 01/11] Update about release page --- en/docs/next/api-portal/about-this-release.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/en/docs/next/api-portal/about-this-release.md b/en/docs/next/api-portal/about-this-release.md index b1baa6820..d5d191a5d 100644 --- a/en/docs/next/api-portal/about-this-release.md +++ b/en/docs/next/api-portal/about-this-release.md @@ -137,17 +137,15 @@ To install and run it, follow the [Getting started](getting-started.md) guide. None. This is the first release, so there is no earlier behavior to improve on. -## Compatible versions and requirements +## Compatible product versions -The distribution bundles the Platform API control plane, so the two are versioned and shipped together—no separate compatibility check is needed. Verify the following in your own environment: +The API Portal & MCP Hub shares a control plane with AI Workspace. The following product version was tested with this release: -| Component | Supported | -|---|---| -| Database | SQLite, PostgreSQL, or Microsoft SQL Server. See [Set up the database](setting-up/database.md) | -| Container runtime | Docker with the Compose plugin | -| Identity provider | Any OIDC-compliant provider issuing JWT access tokens. See [Connect an identity provider](setting-up/authentication/connect-an-identity-provider.md) | +| Product | Compatible version | +|---------|--------------------| +| WSO2 AI Workspace | 1.0.0 | -Full prerequisites are listed in the [Getting started](getting-started.md) guide. +The distribution bundles the Platform API control plane, so the two are versioned and shipped together—no separate compatibility check is needed. Full prerequisites are listed in the [Getting started](getting-started.md) guide. ## Key changes From 03fd67b3d624732286936815949e5d4822d674d1 Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake Date: Thu, 6 Aug 2026 11:52:30 +0530 Subject: [PATCH 02/11] Update idp config details --- .../authentication/connect-an-identity-provider.md | 2 ++ .../api-portal/setting-up/authentication/overview.md | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/en/docs/next/api-portal/setting-up/authentication/connect-an-identity-provider.md b/en/docs/next/api-portal/setting-up/authentication/connect-an-identity-provider.md index 9bf41c3a3..7b4df7a12 100644 --- a/en/docs/next/api-portal/setting-up/authentication/connect-an-identity-provider.md +++ b/en/docs/next/api-portal/setting-up/authentication/connect-an-identity-provider.md @@ -84,6 +84,8 @@ The portal's Management API (`/api-portal/api/v0.9`) guards each operation with The table also aliases `ap_admin` and `ap_subscriber` onto those same grants, because those are the role names the Platform API mints. Map your IdP's groups onto any of the four names, or add an entry of your own for a narrower grant. The portal validates every scope in the table against its OpenAPI specification at startup, so an undeclared `dp:*` scope fails startup rather than surfacing later as a role that logs in and is denied every request. +When a token carries several roles, the effective scopes are the union of every matching entry—most permissive wins. A role name the table doesn't list contributes nothing, so the failure mode of a mistyped or unmapped role is a denied request, never an unintended grant. + **Scope mode** (`mode = "scope"`) reads the access token's own `scope` claim. Use it when the IdP mints `dp:*` scopes directly, which means registering all of them in the IdP and granting them to the application. Browser sessions are preauthorized in this mode—the per-operation check is skipped, and page role gating is the authorization that applies to them. Role mode is the lighter integration of the two. Editing the mapping file needs a restart, since the portal reads it at startup. diff --git a/en/docs/next/api-portal/setting-up/authentication/overview.md b/en/docs/next/api-portal/setting-up/authentication/overview.md index 49da38ea6..3115b2d44 100644 --- a/en/docs/next/api-portal/setting-up/authentication/overview.md +++ b/en/docs/next/api-portal/setting-up/authentication/overview.md @@ -37,7 +37,16 @@ Public pages (the API catalog and documentation) are always accessible without a Local authentication delegates credential validation to the Platform API control plane. It requires no external identity provider, which makes it the default for local development and quick trials. -When `[api_portal.auth] mode = "local"`, the portal renders a username and password form and validates the credentials against the Platform API. Users, bcrypt-hashed passwords, and scopes are defined in the Platform API's own configuration, under `[[platform_api.auth.file.users]]`. +When `[api_portal.auth] mode = "local"`, the portal renders a username and password form and validates the credentials against the Platform API. Users, bcrypt-hashed passwords, and roles are defined in the Platform API's own configuration, under `[[platform_api.auth.file.users]]`: + +```toml +[[platform_api.auth.file.users]] +username = "admin" +password_hash = "..." +roles = ["ap_admin"] +``` + +Those role names are what the portal authorizes against. The Platform API mints them into the `roles` claim of the token it issues, and because `authorization.mode = "role"` is the default, the portal expands that claim through its grant table—which aliases `ap_admin` and `ap_subscriber` onto its own `dp_admin` and `dp_subscriber` grants for exactly this reason. The shipped setup therefore works unchanged. See [Choose how privileges reach the token](connect-an-identity-provider.md#step-3-choose-how-privileges-reach-the-token) for what each role grants. ```toml [api_portal.auth] From cd0a7a1510b639083f8856a317d3ef2cb7086d6b Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake Date: Thu, 6 Aug 2026 12:27:57 +0530 Subject: [PATCH 03/11] Improve configuration and ports setup documentation --- .../api-portal/references/configurations.md | 37 +--- .../api-portal/setting-up/configuration.md | 113 ++++++++++++ en/docs/next/api-portal/setting-up/ports.md | 165 ++++++++++++++++++ en/mkdocs.yml | 6 +- 4 files changed, 285 insertions(+), 36 deletions(-) create mode 100644 en/docs/next/api-portal/setting-up/configuration.md create mode 100644 en/docs/next/api-portal/setting-up/ports.md diff --git a/en/docs/next/api-portal/references/configurations.md b/en/docs/next/api-portal/references/configurations.md index 8f3d4135d..90f547c5f 100644 --- a/en/docs/next/api-portal/references/configurations.md +++ b/en/docs/next/api-portal/references/configurations.md @@ -14,42 +14,11 @@ content_type: "reference" # Configurations -The API Portal & MCP Hub 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. +The API Portal & MCP Hub reads its configuration from `configs/config.toml`, layered over built-in defaults (`src/config/configDefaults.js`). This page is the full reference of every supported key. -{% raw %} - -## 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. - -!!! important "Environment variables do not override config keys directly" - There is no automatic `APIP_AP_*` 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`: - -```toml -[api_portal.security] -encryption_key = '{{ env "APIP_AP_SECURITY_ENCRYPTION_KEY" }}' -session_secret = '{{ env "APIP_AP_SECURITY_SESSION_SECRET" }}' -``` +For how that file is loaded, how environment values and mounted files are injected through interpolation tokens, and how to keep sensitive values out of the file, see [Configuration and environment interpolation](../setting-up/configuration.md). -Partial substitution works too—`'foo-{{ env "X" }}'` resolves to `"foo-bar"` if `X=bar`. - -!!! note "`{{ file }}` path allowlist" - `{{ file "/path" }}` only reads from `/etc/api-portal` or `/secrets/api-portal` 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. +{% raw %} ## Server diff --git a/en/docs/next/api-portal/setting-up/configuration.md b/en/docs/next/api-portal/setting-up/configuration.md new file mode 100644 index 000000000..abfcbd699 --- /dev/null +++ b/en/docs/next/api-portal/setting-up/configuration.md @@ -0,0 +1,113 @@ +--- +title: "API Portal configuration and environment interpolation" +description: "How the API Portal & MCP Hub and the Platform API load their config.toml files, inject environment values and mounted files through interpolation tokens, and keep sensitive values out of the config file." +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/configuration/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/configuration.md +tags: + - cloud + - api-portal + - configuration + - interpolation +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "reference" +--- + +# API Portal configuration and environment interpolation + +The API Portal stack has two services: the API Portal & MCP Hub itself and the Platform API control plane it authenticates against. Each reads its configuration from a TOML file (`config.toml`) layered over built-in defaults. + +This page explains how each service loads its config file. It also covers how environment values and mounted files are injected through interpolation tokens, and how to keep sensitive values out of the file. For the full reference of every supported key, see [Configurations](../references/configurations.md). For provisioning the keys, certificates, and credentials those tokens resolve to, see [Getting started](../getting-started.md). + +## How configuration is loaded + +Each service reads a TOML file mounted into its container, layered over that service's built-in defaults: + +- **API Portal & MCP Hub** — `/app/configs/config.toml`; every key lives under the `[api_portal]` table. Defaults come from `src/config/configDefaults.js`. +- **Platform API** — `/etc/platform-api/config.toml`; every key lives under the `[platform_api]` table. + +Precedence runs lowest to highest: built-in defaults, then `configs/config.toml` with its interpolation tokens resolved. + +The per-service namespacing (`[api_portal]`, `[platform_api]`, `[ai_workspace]`) lets one `config.toml` hold multiple services' sections side by side without their keys colliding — each service reads only its own table. The shipped stack relies on this: the API Portal and AI Workspace containers mount the same file and each ignore the other's section. + +`configs/config-template.toml` documents every supported key with its default as a plain literal. It's a reference copy, not the file the portal reads. + +!!! important "Environment variables don't override config keys directly" + There is **no prefix that auto-maps environment variables onto config keys.** An environment value reaches a setting **only** through an explicit interpolation token written into the config file, resolved when the file is loaded. A key written as a plain literal — or absent from the file — ignores the matching variable entirely. + +## Interpolation tokens + +Two functions are available inside `config.toml`: + +{% raw %} + +| Token | Behavior | +|-------|----------| +| `{{ env "NAME" }}` | Substitutes the value of environment variable `NAME`. **Fails closed** — an unset or empty variable aborts startup rather than falling 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 — for injecting secrets from a mounted volume rather than an environment variable. Always required: a missing, unreadable, oversized, or disallowed path is a hard startup error. | + +An example from the shipped API Portal `config.toml`: + +```toml +[api_portal.server] +port = '{{ env "APIP_AP_SERVER_PORT" "9543" }}' + +[api_portal.security] +encryption_key = '{{ file "/etc/api-portal/keys/encryption.key" }}' +``` + +{% endraw %} + +Partial substitution works too — {% raw %}`'foo-{{ env "X" }}'`{% endraw %} resolves to `foo-bar` when `X=bar`. + +A templated value is coerced to its natural type after substitution, so `"true"` becomes a boolean and `"9543"` becomes a number. A plain TOML literal keeps its native type and is never passed through coercion. + +!!! note "The variable name is a naming convention, not a prefix override" + By convention each token names the key's dotted path, uppercased with dots as underscores, behind a per-service prefix. The prefixes are `APIP_AP_` for the API Portal, `APIP_CP_` for the Platform API, and `APIP_AIW_` for AI Workspace. For example, `[api_portal.server] port` becomes `APIP_AP_SERVER_PORT`. The loader doesn't interpret the prefix — the name is only the literal string you pass to the interpolation function. You can rename any variable, as long as you edit the matching token in `config.toml` to agree. + +### Which variables your deployment reads + +Because the variable names live in `config.toml`, the config file is the authoritative list for your deployment — not this page. To see which settings your stack injects from the environment, search the mounted `config.toml` for {% raw %}`{{ env`{% endraw %} and read the name out of each token. + +For every configurable option and the tokens the shipped files carry, refer to the config templates: [API Portal](https://github.com/wso2/api-platform/blob/main/portals/api-portal/configs/config-template.toml) and [Platform API](https://github.com/wso2/api-platform/blob/main/platform-api/config/config-template.toml). + +## Sensitive values in `config.toml` + +This section covers credentials the services need to start — the at-rest encryption key, the session secret, the OpenID Connect (OIDC) client secret, and any database password. It's a separate mechanism from the credentials the portal stores for applications and API keys, which live encrypted in its database. + +Never write a sensitive value as a literal in `config.toml`, and never hardcode one in `docker-compose.yaml`. Reference each with an interpolation token — from a mounted file, or from an environment variable: + +{% raw %} + +```toml +# The shipped config.toml reads both portal secrets from mounted files: +[api_portal.security] +encryption_key = '{{ file "/etc/api-portal/keys/encryption.key" }}' +session_secret = '{{ file "/etc/api-portal/keys/session-secret" }}' + +# OIDC client secret (idp mode) - env var, or preferably a mounted file: +[api_portal.auth.idp] +client_secret = '{{ env "APIP_AP_AUTH_IDP_CLIENT_SECRET" }}' +# client_secret = '{{ file "/secrets/api-portal/oidc_client_secret" }}' +``` + +{% endraw %} + +None of these carries a default, so each is a required secret. Both forms fail closed: if the variable is unset or empty, or the file is missing or outside the allowed source directories, the service refuses to start rather than run with an empty credential. + +A {% raw %}`{{ file }}`{% endraw %} path must live under an allowed directory — `/etc/api-portal` or `/secrets/api-portal` for the portal, `/etc/platform-api` or `/secrets/platform-api` for the Platform API. Override the list with the shared `APIP_CONFIG_FILE_SOURCE_ALLOWLIST` (comma-separated; it **replaces** the defaults rather than extending them). That variable is read straight from the process environment rather than through {% raw %}`{{ env }}`{% endraw %}, since it gates interpolation itself. Files are also capped at 1 MiB, and traversal sequences and symlinks escaping the allowlist are rejected before the read. + +## Where the values come from + +Provisioning the keys, certificates, and credentials the tokens resolve to is part of setting the stack up, not part of how the config loader works. For those steps, see: + +- [Run the setup script](../getting-started.md#step-2-run-the-setup-script) — what a fresh stack is given, and where each artifact lands under `resources/certificates/` and `resources/keys/`. +- The script is idempotent: re-running it fills in only what's missing. To rotate a secret, remove it from `api-platform.env` or delete the relevant file, then re-run. +- `api-platform.env` is the file Compose loads into every container, so it's where an environment value a token names is set. + +## Related + +- [Configurations](../references/configurations.md): the full reference of every supported key +- [Getting started](../getting-started.md): provision the keys, certificates, and credentials the tokens resolve to +- [Change the ports the API Portal uses](ports.md): move the stack off its default ports diff --git a/en/docs/next/api-portal/setting-up/ports.md b/en/docs/next/api-portal/setting-up/ports.md new file mode 100644 index 000000000..f64286e9b --- /dev/null +++ b/en/docs/next/api-portal/setting-up/ports.md @@ -0,0 +1,165 @@ +--- +title: "Change the ports the API Portal uses" +description: "Move the API Portal & MCP Hub and the Platform API off their default ports, either through the APIP_AP_SERVER_PORT variable or by editing the Compose port mappings." +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/ports/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/ports.md +tags: + - cloud + - api-portal + - configuration +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Change the ports the API Portal uses + +The stack listens on these ports by default: + +| Port | Service | Purpose | +|------|---------|---------| +| `9543` | API Portal & MCP Hub | HTTPS — the browser entry point | +| `9243` | Platform API | HTTPS — the local-auth backend and control plane | +| `9643` | AI Workspace | HTTPS — only when you enable the `ai-workspace` profile | + +If another process on your machine holds one of these, or your organization reserves it, move the stack off it. The two services work differently: the portal's port is driven by a single environment variable, while the Platform API's is fixed in `docker-compose.yaml`. + +!!! note "Two stacks can't share a host" + Unpacking the distribution twice and starting both fails — each copy binds `9543` and `9243`. Change the ports on the second copy, or stop the first. + +## Change the API Portal port + +`docker-compose.yaml` reads the same variable on both sides of the portal's mapping and passes it into the container, and the shipped `configs/config.toml` binds the listener from it: + +{% raw %} + +```yaml + environment: + APIP_AP_SERVER_PORT: ${APIP_AP_SERVER_PORT:-9543} + ports: + - "${APIP_AP_SERVER_PORT:-9543}:${APIP_AP_SERVER_PORT:-9543}" +``` + +```toml +[api_portal.server] +port = '{{ env "APIP_AP_SERVER_PORT" "9543" }}' +``` + +{% endraw %} + +So one variable moves the published port and the listener together — no Compose edit required. + +1. Set the variable in `api-platform.env`, the file Compose loads into every service: + + ```bash + APIP_AP_SERVER_PORT=8443 + ``` + +2. Update `base_url` in `configs/config.toml`. **This does not follow the variable** — the shipped file writes it as a plain literal: + + ```toml + [api_portal.server] + base_url = "https://localhost:8443" + ``` + + Leaving it at `9543` doesn't stop the portal from serving. It only affects the absolute URLs embedded in generated AI-agent prompts, which would point at a port nothing listens on. To drive it from the environment instead, replace the literal with the token the config template ships: + + {% raw %} + + ```toml + base_url = '{{ env "APIP_AP_SERVER_BASE_URL" "https://localhost:9543" }}' + ``` + + {% endraw %} + +3. Recreate the containers, and open the portal on the new port: + + ```bash + docker compose up --force-recreate + ``` + + The portal is served under the `/api-portal` path prefix, so the full URL becomes `https://localhost:8443/api-portal/default/views/default`. + +## Change the Platform API port + +The Platform API's mapping is fixed at `9243:9243`, so this one is a Compose edit. Decide first whether you need the *published* port moved or the *listener* moved — the portal reaches the Platform API over the Compose network, not through the published port. + +### Remap the published port only + +Enough when the conflict is on your own machine. The container keeps listening on 9243, and Docker publishes it elsewhere: + +```yaml +services: + platform-api: + ports: + - "8244:9243" +``` + +Leave the container side and the `healthcheck` entry alone — both run inside the container, where 9243 still applies. Leave `platform_api_url` alone too, for the same reason (see [Two settings that aren't interchangeable](#two-settings-that-arent-interchangeable)). + +### Change the listener port + +Choose this when something inside the Docker network needs the new port. Add a `[platform_api.server.https]` table to `configs/config.toml` — the shipped file omits it, so include the certificate paths, which the Platform API requires on its HTTPS listener: + +```toml +[platform_api.server.https] +enabled = true +port = 8244 +cert_file = "/app/data/certs/cert.pem" +key_file = "/app/data/certs/key.pem" +``` + +Then update both sides of the mapping and the health check, which runs inside the container: + +```yaml +services: + platform-api: + ports: + - "8244:8244" + healthcheck: + test: ["CMD", "curl", "-fk", "https://localhost:8244/health"] +``` + +Finally point the portal at the new listener: + +```bash +APIP_AP_AUTH_LOCAL_PLATFORM_API_URL=https://platform-api:8244 +``` + +## Two settings that aren't interchangeable + +A port change touches values on either side of the Compose network boundary: + +| Setting | Who connects to it | Value | +|---------|--------------------|-------| +| `[api_portal.auth.local] platform_api_url` | The API Portal container, over the Compose network | A full URL using the internal service name and the **container** port — `https://platform-api:9243` | +| The published Platform API port | A browser, the `ap` CLI, or `curl` on your host | The **host** side of the Compose mapping | + +Setting `platform_api_url` to a published host port is the common mistake: the portal container can't resolve `localhost` to your machine, so every login fails while the Platform API itself looks healthy. Conversely, remapping only the published port and then editing `platform_api_url` breaks a portal that was working. + +## Ports in an OIDC setup + +OIDC redirect URLs carry the port, and both must sit under the portal's `/api-portal` mount. After a port change, update these in `configs/config.toml` and register the same values in your identity provider: + +```toml +[api_portal.auth.idp] +callback_url = "https://localhost:8443/api-portal//callback" +logout_redirect_uri = "https://localhost:8443/api-portal/" +``` + +See [Connect an identity provider](authentication/connect-an-identity-provider.md). + +## Serving plain HTTP + +If a trusted upstream terminates TLS, turn the portal's own TLS off rather than changing ports — the single listener then serves plain HTTP on the same port: + +```bash +APIP_AP_SERVER_HTTPS_ENABLED=false +``` + +There is no self-signed fallback: with HTTPS enabled, `cert_file` and `key_file` must both resolve. + +## Related + +- [Configurations](../references/configurations.md) — how interpolation tokens deliver values into `config.toml` +- [Getting started](../getting-started.md) — the quickstart these defaults come from diff --git a/en/mkdocs.yml b/en/mkdocs.yml index 1922a6f04..65aa15e57 100644 --- a/en/mkdocs.yml +++ b/en/mkdocs.yml @@ -712,12 +712,14 @@ nav: - Theming: next/api-portal/theming.md - Tutorials: - Secured API End to End: next/api-portal/tutorials/secured-api-end-to-end.md - - Set up Asgardeo: next/api-portal/tutorials/asgardeo-as-idp.md - Setting Up: + - Configuration & Interpolation: next/api-portal/setting-up/configuration.md + - Ports: next/api-portal/setting-up/ports.md + - Database: next/api-portal/setting-up/database.md - Authentication: - Overview: next/api-portal/setting-up/authentication/overview.md - Connect an Identity Provider: next/api-portal/setting-up/authentication/connect-an-identity-provider.md - - Set up the Database: next/api-portal/setting-up/database.md + - Set up Asgardeo: next/api-portal/tutorials/asgardeo-as-idp.md - Artifact types: next/api-portal/artifact-types.md - Design Mode: next/api-portal/setting-up/design-mode.md - Admin Settings: From 018fb7abbc810a61c0e0d314dcdbb4ee83d7bd9f Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake Date: Thu, 6 Aug 2026 14:28:53 +0530 Subject: [PATCH 04/11] Refactor the doc pages --- .../api-portal/admin-settings/api-content.md | 8 +- ...i-workflows.md => manage-api-workflows.md} | 0 .../next/api-portal/consume-an-api/api-key.md | 2 +- .../api-portal/consume-an-api/overview.md | 4 +- .../discover-apis/ai-agent-discovery.md | 2 +- .../discover-apis/api-documentations.md | 68 ----------- .../api-portal/discover-apis/api-overview.md | 72 ------------ .../api-portal/discover-apis/api-search.md | 74 ------------ .../api-portal/discover-apis/browse-apis.md | 109 ++++++++++++++++++ .../next/api-portal/discover-apis/overview.md | 77 +++++++++++++ en/docs/next/api-portal/getting-started.md | 2 +- .../next/api-portal/manage-subscriptions.md | 4 +- ...r-mcp-servers.md => browse-mcp-servers.md} | 10 +- .../mcp-servers/connect-to-an-mcp-server.md | 2 +- .../api-portal/mcp-servers/mcp-registry.md | 2 +- .../next/api-portal/mcp-servers/overview.md | 2 +- 16 files changed, 205 insertions(+), 233 deletions(-) rename en/docs/next/api-portal/admin-settings/{managing-api-workflows.md => manage-api-workflows.md} (100%) delete mode 100644 en/docs/next/api-portal/discover-apis/api-documentations.md delete mode 100644 en/docs/next/api-portal/discover-apis/api-overview.md delete mode 100644 en/docs/next/api-portal/discover-apis/api-search.md create mode 100644 en/docs/next/api-portal/discover-apis/browse-apis.md create mode 100644 en/docs/next/api-portal/discover-apis/overview.md rename en/docs/next/api-portal/mcp-servers/{discover-mcp-servers.md => browse-mcp-servers.md} (93%) diff --git a/en/docs/next/api-portal/admin-settings/api-content.md b/en/docs/next/api-portal/admin-settings/api-content.md index 8e13abcb1..8c81cbedb 100644 --- a/en/docs/next/api-portal/admin-settings/api-content.md +++ b/en/docs/next/api-portal/admin-settings/api-content.md @@ -14,7 +14,7 @@ content_type: "how-to" # Customize an API's content -By default, an API's [overview page](../discover-apis/api-overview.md) is generated from its specification—Endpoints, Resources, Scopes, and the subscription plans panel. **API content** lets you replace that body with your own, per API, and attach the images and documents that go with it. +By default, an API's [overview page](../discover-apis/api-search.md#open-an-api) is generated from its specification—Endpoints, Resources, Scopes, and the subscription plans panel. **API content** lets you replace that body with your own, per API, and attach the images and documents that go with it. This is narrower than [theming](../theming.md): a theme restyles every page in a view, while API content changes one API's overview body and leaves everything else alone. @@ -67,7 +67,7 @@ Without it, the portal falls back to a generated avatar showing the first two le ## Attach documents -Files under `docs/` become the documents listed on the API's [documentation page](../discover-apis/api-documentations.md), and are served to AI agents as raw Markdown. +Files under `docs/` become the documents listed on the API's [documentation page](../discover-apis/api-search.md#read-the-specification-and-try-it), and are served to AI agents as raw Markdown. The **first-level directory name becomes the section heading** in the documentation navigation. Files placed directly in `docs/` land in a section called **Other**: @@ -114,8 +114,8 @@ samples/apis/my-api-v1.0/ ## Related -- [API Overview](../discover-apis/api-overview.md): the generated page your content replaces -- [API Documentation](../discover-apis/api-documentations.md): where `docs/` files surface +- [API Overview](../discover-apis/api-search.md#open-an-api): the generated page your content replaces +- [API Documentation](../discover-apis/api-search.md#read-the-specification-and-try-it): where `docs/` files surface - [Theming](../theming.md): restyle every page in a view, rather than one API's body - [Manage APIs](manage-apis.md): the wizard this is the last step of - [API Content](../rest-api/api-content.md): upload, replace, and delete content through the Management API diff --git a/en/docs/next/api-portal/admin-settings/managing-api-workflows.md b/en/docs/next/api-portal/admin-settings/manage-api-workflows.md similarity index 100% rename from en/docs/next/api-portal/admin-settings/managing-api-workflows.md rename to en/docs/next/api-portal/admin-settings/manage-api-workflows.md diff --git a/en/docs/next/api-portal/consume-an-api/api-key.md b/en/docs/next/api-portal/consume-an-api/api-key.md index 470dc25b6..133f35f28 100644 --- a/en/docs/next/api-portal/consume-an-api/api-key.md +++ b/en/docs/next/api-portal/consume-an-api/api-key.md @@ -54,7 +54,7 @@ The two answer different questions: the key says who you are, and the subscripti ## Check it before writing code -Open the API's [documentation page](../discover-apis/api-documentations.md) and use the **Try It** console on a REST API, or **Tryout** on a GraphQL API, which has a dedicated **API Key** tab with fields for the header name and value. A call that succeeds there confirms both the header names and the key itself. +Open the API's [documentation page](../discover-apis/api-search.md#read-the-specification-and-try-it) and use the **Try It** console on a REST API, or **Tryout** on a GraphQL API, which has a dedicated **API Key** tab with fields for the header name and value. A call that succeeds there confirms both the header names and the key itself. ## Related diff --git a/en/docs/next/api-portal/consume-an-api/overview.md b/en/docs/next/api-portal/consume-an-api/overview.md index a5440e3f2..00e8e4724 100644 --- a/en/docs/next/api-portal/consume-an-api/overview.md +++ b/en/docs/next/api-portal/consume-an-api/overview.md @@ -33,7 +33,7 @@ Read the API's own specification—it's authoritative, and the portal derives it 3. Read the `security` entries for what it *requires*. A root-level `security` block applies to every operation; an operation can override it with its own. A scheme that's defined but never referenced isn't required. 4. Check for a subscription-key parameter. When the specification declares one, the portal shows a subscription token after you subscribe, along with the header to send it in. -Two shortcuts on the API's [overview page](../discover-apis/api-overview.md) tell you the same thing faster: +Two shortcuts on the API's [overview page](../discover-apis/api-search.md#open-an-api) tell you the same thing faster: - An **API Keys** button appears for REST, WebSocket, and WebSub APIs whose specification declares API key security. The portal offers no key generation in the UI for GraphQL, SOAP, or MCP artifacts, even when their specification declares an `apiKey` scheme. For those, use the Management API: [API Keys](../rest-api/api-keys.md) for GraphQL and SOAP APIs, and [MCP Server Keys](../rest-api/mcp-server-keys.md) for MCP servers. - A **Subscription plans** panel appears only for APIs with plans, which is what a subscription token comes from. @@ -58,7 +58,7 @@ This one needs the most setup, because the OAuth application lives in a key mana ## Try before you wire it up -For REST APIs, the **Try It** console on the [documentation page](../discover-apis/api-documentations.md) sends real requests from your browser. Paste the same credentials you'd use from code—it's the fastest way to confirm you have the right header names before writing a client. +For REST APIs, the **Try It** console on the [documentation page](../discover-apis/api-search.md#read-the-specification-and-try-it) sends real requests from your browser. Paste the same credentials you'd use from code—it's the fastest way to confirm you have the right header names before writing a client. ## Related diff --git a/en/docs/next/api-portal/discover-apis/ai-agent-discovery.md b/en/docs/next/api-portal/discover-apis/ai-agent-discovery.md index df5d3a6d0..b80f98156 100644 --- a/en/docs/next/api-portal/discover-apis/ai-agent-discovery.md +++ b/en/docs/next/api-portal/discover-apis/ai-agent-discovery.md @@ -112,7 +112,7 @@ A typical agent discovery flow looks like this: 5. **Fetch the specification separately.** When the agent needs the specification as a parseable file rather than as inlined text, it retrieves the `specification.*` endpoint for the API's type. 6. **Follow a workflow.** If a published workflow matches the task, the agent retrieves the Arazzo specification and agent prompt, then follows a vetted, step-by-step call sequence instead of reasoning from scratch. -The **Try with AI** button on an [API's overview page](api-overview.md) hands this flow to an agent directly. It produces a prompt that points the agent at that API's `.md` URL and asks it to summarize the API before doing anything else. +The **Try with AI** button on an [API's overview page](api-search.md#open-an-api) hands this flow to an agent directly. It produces a prompt that points the agent at that API's `.md` URL and asks it to summarize the API before doing anything else. ## Visibility controls diff --git a/en/docs/next/api-portal/discover-apis/api-documentations.md b/en/docs/next/api-portal/discover-apis/api-documentations.md deleted file mode 100644 index af951568e..000000000 --- a/en/docs/next/api-portal/discover-apis/api-documentations.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: "View API documentation in the API Portal & MCP Hub" -description: "Read an API's specification, try its operations from the browser, and open the guides its publisher attached, on the documentation page." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/api-documentations/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/api-documentations.md -tags: - - cloud - - api-portal - - discover-apis -author: WSO2 API Platform Documentation Team -last_updated: 2026-07-31 -content_type: "how-to" ---- - -# API documentation - -The documentation page holds everything the publisher wrote about an API: its full specification, an interactive console for calling operations, and any guides attached alongside. Where the [overview page](api-overview.md) summarizes the API, this is where you work out how to call it. - -## Open the documentation page - -1. Go to **APIs** in the sidebar and open the API you want. -2. Click **Documentation** in the header, or select **Documentation** under **APIs** in the sidebar. - - ![](../../../assets/img/standalone-devportal/discover-apis/api-documentations/menu-api-doc.png){style="max-width:250px;"} - -The page opens on the API's specification. A navigation pane on the left lists everything else available. - -!!! note - SOAP APIs have no **Documentation** button. Their overview page offers a **Download** button for the WSDL file instead. - -## Navigate the documentation - -The left pane groups entries under a heading per section: - -- **SPECIFICATION** holds the API's own specification, listed as **API Definition** (or **MCP Playground** for an MCP server). For WebSocket, WebSub, and GraphQL APIs it also holds **Tryout**, which opens the same specification in an interactive client. -- One group per document type the publisher used, such as **Other**, listing each attached document by name. - -Select any entry to load it in the content pane on the right. A badge above the content names the file you're reading, such as `openapi.yaml` or `getting-started.md`. - -## Read the API specification - -**API Definition** renders the specification in a viewer chosen for the API type: - -| API type | Viewer | What you can do | -|---|---|---| -| REST | OpenAPI reference | Read every operation, parameter, and response schema, and call operations from the built-in **Try It** console | -| GraphQL | Schema viewer | Browse types, queries, and mutations. **Tryout** opens GraphiQL, with an endpoint selector and fields for an OAuth2 token or API key | -| WebSocket, WebSub | AsyncAPI viewer | Read the channels and message payloads. **Tryout** opens a client that connects to the endpoint | -| MCP server | MCP Playground | Inspect the server's tools and invoke them with a bearer token | - -The **Try It** console on a REST API calls the endpoint straight from your browser, so the API's gateway has to return CORS headers for the portal's origin. Supply every credential the operation requires, the same way your client would: an `Authorization: Bearer` header for OAuth2, the API key header for key-secured APIs, and—where the API declares one—the subscription header its specification names. - -## Read an attached document - -Publishers attach guides to an API to cover what a specification can't express—authentication walkthroughs, worked examples, known limitations. Select a document in the left pane to read it. - -An attached document renders in the content pane, as in this example covering a token-based subscription: - -![Attached document showing an authentication headers table, a subscription plans table, and numbered steps for consuming the API](../../../assets/img/standalone-devportal/discover-apis/api-documentations/api-doc-md.png) - -Every document is also served as raw Markdown for AI agents. See [AI Agent Discovery](ai-agent-discovery.md) for the endpoints. - -## Related - -- [Search APIs](api-search.md): find the API you want to view -- [API Overview](api-overview.md): endpoints, resources, and subscription plans at a glance -- [Consume an API Secured with OAuth2](../consume-an-api/oauth2.md): get a token before using the try-out console -- [Consume an API Secured with an API Key](../consume-an-api/api-key.md) diff --git a/en/docs/next/api-portal/discover-apis/api-overview.md b/en/docs/next/api-portal/discover-apis/api-overview.md deleted file mode 100644 index 1c949ca06..000000000 --- a/en/docs/next/api-portal/discover-apis/api-overview.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: "View the API overview page in the API Portal & MCP Hub" -description: "See an API's version, description, tags, endpoints, resources, scopes, and subscription plans on its overview page before subscribing or trying it out." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/api-overview/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/api-overview.md -tags: - - cloud - - api-portal - - discover-apis -author: WSO2 API Platform Documentation Team -last_updated: 2026-07-31 -content_type: "how-to" ---- - -# API overview - -Every API's overview page summarizes what the API does, how to reach it, and how to start consuming it. Read it before you dig into the full [documentation](api-documentations.md). - -## View an API's overview page - -1. Go to **APIs** in the sidebar and [search or browse](api-search.md) for the API you want. -2. Click the API's card to open it. The overview page loads by default. - - The overview page gathers the API's identity, endpoints, operations, and plans on one screen: - - ![API overview page with header badges, endpoint URLs, a resources list, and a subscription plans panel](../../../assets/img/standalone-devportal/discover-apis/api-overview/api-overview.png) - -## What you'll find - -The header shows the API's icon, name, version, and description, along with tags (informational only, not clickable filters) and the following badges: - -- **Type**: the API type—REST, GraphQL, WebSocket, WebSub, or SOAP -- **AI Ready**: shown when the API is visible to AI agents -- **Deprecated**: shown when the API has been deprecated - -### Action buttons - -- **Subscribe**: jumps down to the subscription plans. Shown when the API has plans and you aren't subscribed to one yet -- **Documentation**: opens the API's full documentation. SOAP APIs show a **Download** button for the WSDL file instead -- **Try with AI**: opens a modal with a ready-made prompt that briefs an AI agent on the API, using its [machine-readable documentation](ai-agent-discovery.md). Shown when the API is agent-visible. From the modal you can copy the prompt, download it as a `.txt` file, or send it straight to an assistant with **Run in Claude**. -- **API Keys**: opens the API Keys page, where you generate a key. Shown for REST, WebSocket, and WebSub APIs whose specification declares API key security, and never for GraphQL, SOAP, or MCP artifacts. - -### Page sections - -- **Endpoints**: the Production and Sandbox base URLs, each with a copy button -- **Resources**: for REST and SOAP APIs, every operation with its HTTP method, path, and summary -- **Channels**: for WebSocket and WebSub APIs, every channel. WebSocket channels carry both a PUB and a SUB badge; WebSub channels carry SUB only -- **Scopes**: for REST and SOAP APIs, the OAuth2 scopes the API defines. The section states when the API defines none -- **Subscription plans**: a side panel listing the plans available for this API, such as Gold and Bronze, with the rate limit each one enforces. - -GraphQL APIs show the Endpoints section only. Their operations live in the schema, which you reach through **Documentation**. - -### Subscribing from this page - -Each plan in the **Subscription plans** panel carries its own button: - -- **Subscribe** creates a subscription to that plan and shows the subscription token, which you send in the header the API's specification names, commonly `Subscription-Key`. If you aren't signed in, the button takes you to the login page first. -- **View subscription** replaces it on the plan you already hold, and opens a dialog where you can reveal or copy the token, regenerate it, suspend the subscription, or unsubscribe. - -For the full flow, see [Manage Subscriptions](../manage-subscriptions.md). - -!!! note - Model Context Protocol (MCP) servers have their own overview page with a similar layout. It shows the server's **MCP Server URL**, **Tools**, **Resources**, and **Prompts**, plus an **MCP Server Configuration** snippet you can paste into an MCP client. See [Discover MCP Servers](../mcp-servers/discover-mcp-servers.md). - -## Related - -- [Search APIs](api-search.md): find the API you want to open -- [Customize an API's Content](../admin-settings/api-content.md): replace this generated body with your own -- [API Documentation](api-documentations.md): full endpoint, schema, and security details -- [Manage Subscriptions](../manage-subscriptions.md): subscribe, switch plans, and manage your subscription token -- [Manage API Keys](../manage-api-keys.md) -- [AI Agent Discovery](ai-agent-discovery.md): the machine-readable documentation behind **Try with AI** diff --git a/en/docs/next/api-portal/discover-apis/api-search.md b/en/docs/next/api-portal/discover-apis/api-search.md deleted file mode 100644 index 95a366bbc..000000000 --- a/en/docs/next/api-portal/discover-apis/api-search.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -title: "Search APIs in the API Portal & MCP Hub" -description: "Browse and search published APIs in the API Portal & MCP Hub by name, version, type, description, tags, or documentation content." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/api-search/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/api-search.md -tags: - - cloud - - api-portal - - discover-apis -author: WSO2 API Platform Documentation Team -last_updated: 2026-07-31 -content_type: "how-to" ---- - -# Search APIs - -The API listing page is where you browse everything published to the API Portal & MCP Hub and narrow it down to the API you need. - -## Browse the API listing - -Click **APIs** in the sidebar. The listing page shows a card for every published API, above a count of how many are listed. - -The listing shows one card per API, with the search bar above it: - -![API listing page with a search bar and four API cards, each showing type badges, tags, plan count, and a Subscribe button](../../../assets/img/standalone-devportal/discover-apis/api-search/api-listing-page.png) - -Each card shows: - -- The API's icon (or its initials), name, and version -- A **Type** badge: REST, GraphQL, WebSocket, WebSub, or SOAP -- An **AI Ready** badge when the API is visible to AI agents, and a **Deprecated** badge when the API has been deprecated -- The description, and any tags the publisher added -- The number of subscription plans and a **Subscribe** button, when the API has plans -- A **Subscribed** ribbon, when you already hold a subscription to the API - -Click a card to open the API's [overview page](api-overview.md). - -Model Context Protocol (MCP) servers are listed separately. Click **MCP Servers** in the sidebar to browse them the same way. - -!!! note - A listing covers one [view](../admin-settings/manage-views.md), and an API appears in it only if one of the API's labels is mapped to that view. If an API you expect is missing, ask your portal admin to check its labels. - -## Search for an API - -1. Type a term into the search bar at the top of the listing page. -2. Press Enter. - -The page reloads with your term applied as a `query` parameter, and the results bar reports how many APIs matched. To return to the full listing, clear the search bar and press Enter again. - -![Search bar with "Naviga" query returning a single matching APIs count of 1 and the Navigation API WebSocket card](../../../assets/img/standalone-devportal/discover-apis/api-search/search-result.png) - -### What a search term matches - -A search takes one free-text term rather than a set of separate fields. Which fields it compares that term against depends on the database the portal runs on—see the per-database differences below. - -| Matched against | Example | -|---|---| -| Name | `Navigation` finds the Navigation API | -| Version | `v3.5` finds every API published at that version | -| Description | `catalog` finds any API described as a catalog | -| Type | `RestApi` finds the REST APIs | -| Tags | `finance` finds every API tagged `finance` | -| Attached documents and the API specification | `webhook` finds an API whose getting-started guide mentions webhooks | - -Two details depend on which database backs your deployment: - -- **PostgreSQL** searches attached documents and the API specification alongside the metadata, and matches whole words and their grammatical variants through English full-text search. -- **SQLite** (the default) and **SQL Server** search the metadata and tags only, and match substrings, so `Naviga` finds the Navigation API. - -## Related - -- [API Overview](api-overview.md): open an API's card to see its endpoints, resources, and subscription plans -- [API Documentation](api-documentations.md): full endpoint, schema, and security details -- [Manage Views](../admin-settings/manage-views.md): how admins decide which APIs a view lists diff --git a/en/docs/next/api-portal/discover-apis/browse-apis.md b/en/docs/next/api-portal/discover-apis/browse-apis.md new file mode 100644 index 000000000..88d13a6c7 --- /dev/null +++ b/en/docs/next/api-portal/discover-apis/browse-apis.md @@ -0,0 +1,109 @@ +--- +title: "Browse APIs in the API Portal & MCP Hub" +description: "Browse and search published APIs in the API Portal & MCP Hub by name, version, type, description, tags, or documentation content." +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/api-search/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/api-search.md +tags: + - cloud + - api-portal + - discover-apis +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# Browse APIs + +The API listing page is where you browse everything published to the API Portal & MCP Hub and narrow it down to the API you need. + +## Browse the API listing + +Click **APIs** in the sidebar. The listing page shows a card for every published API, above a count of how many are listed. + +The listing shows one card per API, with the search bar above it: + +![API listing page with a search bar and four API cards, each showing type badges, tags, plan count, and a Subscribe button](../../../assets/img/standalone-devportal/discover-apis/api-search/api-listing-page.png) + +Each card shows: + +- The API's icon (or its initials), name, and version +- A **Type** badge: REST, GraphQL, WebSocket, WebSub, or SOAP +- An **AI Ready** badge when the API is visible to AI agents, and a **Deprecated** badge when the API has been deprecated +- The description, and any tags the publisher added +- The number of subscription plans and a **Subscribe** button, when the API has plans +- A **Subscribed** ribbon, when you already hold a subscription to the API + +Click a card to open the API. Model Context Protocol (MCP) servers are listed separately — click **MCP Servers** in the sidebar to browse them the same way. + +!!! note + A listing covers one [view](../admin-settings/manage-views.md), and an API appears in it only if one of the API's labels is mapped to that view. If an API you expect is missing, ask your portal admin to check its labels. + +## Search for an API + +1. Type a term into the search bar at the top of the listing page. +2. Press Enter. + +The page reloads with your term applied as a `query` parameter, and the results bar reports how many APIs matched. To return to the full listing, clear the search bar and press Enter again. + +![Search bar with "Naviga" query returning a single matching APIs count of 1 and the Navigation API WebSocket card](../../../assets/img/standalone-devportal/discover-apis/api-search/search-result.png) + +### What a search term matches + +A search takes one free-text term rather than a set of separate fields. Which fields it compares that term against depends on the database the portal runs on—see the per-database differences below. + +| Matched against | Example | +|---|---| +| Name | `Navigation` finds the Navigation API | +| Version | `v3.5` finds every API published at that version | +| Description | `catalog` finds any API described as a catalog | +| Type | `RestApi` finds the REST APIs | +| Tags | `finance` finds every API tagged `finance` | +| Attached documents and the API specification | `webhook` finds an API whose getting-started guide mentions webhooks | + +Two details depend on which database backs your deployment: + +- **PostgreSQL** searches attached documents and the API specification alongside the metadata, and matches whole words and their grammatical variants through English full-text search. +- **SQLite** (the default) and **SQL Server** search the metadata and tags only, and match substrings, so `Naviga` finds the Navigation API. + +## Open an API + +Clicking a card opens the API's overview page, which gathers its endpoints, operations, and plans on one screen: + +![API overview page with header badges, endpoint URLs, a resources list, and a subscription plans panel](../../../assets/img/standalone-devportal/discover-apis/api-overview/api-overview.png) + +What the page shows depends on the API type. **Resources** lists every operation for REST and SOAP APIs; **Channels** replaces it for WebSocket and WebSub, where WebSocket channels carry both a PUB and a SUB badge and WebSub channels carry SUB only. **Scopes** appears for REST and SOAP. GraphQL APIs show **Endpoints** alone — their operations live in the schema, which you reach through **Documentation**. + +Two header buttons appear conditionally rather than always: + +- **API Keys** — only for REST, WebSocket, and WebSub APIs whose specification declares API key security. Never for GraphQL, SOAP, or MCP artifacts. +- **Try with AI** — only when the API is agent-visible. It opens a ready-made prompt that briefs an agent on the API using its [machine-readable documentation](ai-agent-discovery.md); copy it, download it as a `.txt` file, or send it straight to an assistant with **Run in Claude**. + +Each plan in the **Subscription plans** panel carries its own **Subscribe** or **View subscription** button — see [Manage Subscriptions](../manage-subscriptions.md) for the full flow. + +## Read the specification and try it + +**Documentation** in the header opens the API's specification alongside any guides the publisher attached. The viewer depends on the API type: + +| API type | Viewer | What you can do | +|---|---|---| +| REST | OpenAPI reference | Read every operation, parameter, and response schema, and call operations from the built-in **Try It** console | +| GraphQL | Schema viewer | Browse types, queries, and mutations. **Tryout** opens GraphiQL, with an endpoint selector and fields for an OAuth2 token or API key | +| WebSocket, WebSub | AsyncAPI viewer | Read the channels and message payloads. **Tryout** opens a client that connects to the endpoint | +| MCP server | MCP Playground | Inspect the server's tools and invoke them with a bearer token | + +!!! note + SOAP APIs have no **Documentation** button. Their overview page offers a **Download** button for the WSDL file instead. + +The **Try It** console calls the endpoint straight from your browser, so **the API's gateway has to return CORS headers for the portal's origin**. Supply every credential the operation requires, the same way your client would: an `Authorization: Bearer` header for OAuth2, the API key header for key-secured APIs, and — where the API declares one — the subscription header its specification names. + +Attached guides render in the same pane and cover what a specification can't: authentication walkthroughs, worked examples, known limitations. Every one is also served as raw Markdown for AI agents. + +![Attached document showing an authentication headers table, a subscription plans table, and numbered steps for consuming the API](../../../assets/img/standalone-devportal/discover-apis/api-documentations/api-doc-md.png) + +## Related + +- [APIs](overview.md): what an API is in the portal, and the types it publishes +- [Which Credentials You Need](../consume-an-api/overview.md): work out what an API expects before you call it +- [Manage Subscriptions](../manage-subscriptions.md): subscribe, switch plans, and manage your subscription token +- [Customize an API's Content](../admin-settings/api-content.md): replace the generated page body with your own +- [Manage Views](../admin-settings/manage-views.md): how admins decide which APIs a view lists diff --git a/en/docs/next/api-portal/discover-apis/overview.md b/en/docs/next/api-portal/discover-apis/overview.md new file mode 100644 index 000000000..b2c6db534 --- /dev/null +++ b/en/docs/next/api-portal/discover-apis/overview.md @@ -0,0 +1,77 @@ +--- +title: "APIs in the API Portal & MCP Hub" +description: "What an API is in the API Portal & MCP Hub, the API types it publishes, the two routes APIs take into the catalog, and how they differ from MCP servers." +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/overview/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/overview.md +tags: + - cloud + - api-portal + - apis +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "concept" +--- + +# APIs + +An **API** is an entry in the portal catalog that a developer can discover, subscribe to, and call from their own application code. Each one carries a contract, a landing page, documentation, and the subscription plans it offers. + +APIs sit in the same catalog as MCP servers and share its subscription and credential machinery. What differs is the contract they publish and how a consumer reaches them. + +## API types + +The portal publishes five API types, each with its own contract format: + +| Type | Contract | +|---|---| +| REST | OpenAPI, as `.json`, `.yaml`, or `.yml` | +| WebSocket | AsyncAPI, as `.json`, `.yaml`, or `.yml` | +| WebSub | AsyncAPI, as `.json`, `.yaml`, or `.yml` | +| GraphQL | A GraphQL schema, as `.graphql` or `.gql` | +| SOAP | WSDL, as `.wsdl` or `.xml` | + +The type is fixed when the API is created and determines what its detail page shows and which interactive console it offers. + +## How APIs differ from MCP servers + +An MCP server is another artifact in the same catalog, so most of what you know about APIs carries over. The differences that matter: + +| | API | MCP server | +|---|---|---| +| Contract | OpenAPI, AsyncAPI, GraphQL SDL, or WSDL | A definition listing tools, resources, and prompts | +| Catalog page | **APIs** in the sidebar | **MCP Servers** in the sidebar | +| Detail page sections | Endpoints, Resources or Channels, Scopes | MCP Server URL, Tools, Resources, Prompts | +| Interactive console | Try It, or a type-specific tryout client | MCP Playground | +| Consumed by | Application code you write | An MCP client, configured with the server's URL | +| Subscribed | Through an application that holds the credentials | Directly, without an application | +| Agent visibility | The publisher sets it per API | Always agent-visible; the catalog marks every server **AI Ready** | + +Everything else works the same way. APIs carry tags, labels, icons, subscription plans, and attached documents, and appear in the portal's [machine-readable endpoints](ai-agent-discovery.md) when the publisher marks them agent-visible. + +!!! note + A portal only serves APIs when its operator lists `apis` in `enabled_types`. Leave it out and the sidebar entry, the catalog, and every API route disappear. See [Artifact types](../artifact-types.md). + +## How APIs reach the catalog + +Two routes put an API in the catalog. + +### Registered by an admin + +An admin adds the API through **Settings** → **APIs**, choosing the type, supplying the details and endpoints, uploading the contract, and attaching documentation. See [Manage APIs](../admin-settings/manage-apis.md). + +### Created through the Management API + +The same artifact can be created programmatically, which is the route automation and CI/CD use. It is also the **only** way to add a SOAP API: the admin wizard's type selector offers no SOAP option, so those are posted with `type: SOAP` and a WSDL definition. See [APIs](../rest-api/apis.md) in the Management API reference. + +## Where to go next + +- [Browse APIs](api-search.md): search the catalog, open an API, and read its specification +- [Which Credentials You Need](../consume-an-api/overview.md): work out what an API expects before you call it + +## Related + +- [Manage APIs](../admin-settings/manage-apis.md): the admin side of adding and publishing APIs +- [MCP Servers](../mcp-servers/overview.md): the other half of the catalog +- [AI Agent Discovery](ai-agent-discovery.md): the machine-readable endpoints that expose APIs to agents +- [Artifact types](../artifact-types.md): whether this deployment serves APIs +- [Concepts](../concepts.md): how APIs fit alongside MCP servers, plans, subscriptions, and keys diff --git a/en/docs/next/api-portal/getting-started.md b/en/docs/next/api-portal/getting-started.md index 6846fc5d2..f590d514d 100644 --- a/en/docs/next/api-portal/getting-started.md +++ b/en/docs/next/api-portal/getting-started.md @@ -244,7 +244,7 @@ Refresh the portal—the **Books API** now appears in the catalog. Select the ** ## What's next -- [Search APIs](discover-apis/api-search.md): browse and search the catalog +- [Browse APIs](discover-apis/api-search.md): browse and search the catalog - [MCP Servers](mcp-servers/overview.md): publish and connect to Model Context Protocol servers - [AI Agent Discovery](discover-apis/ai-agent-discovery.md): the `llms.txt` and Markdown endpoints agents use - [Manage Applications](manage-applications.md): set up a container for OAuth2 credentials diff --git a/en/docs/next/api-portal/manage-subscriptions.md b/en/docs/next/api-portal/manage-subscriptions.md index 31c0ea9cc..9b741791b 100644 --- a/en/docs/next/api-portal/manage-subscriptions.md +++ b/en/docs/next/api-portal/manage-subscriptions.md @@ -76,7 +76,7 @@ Cancelling invalidates the subscription token. Anything calling the API with it Not every API has subscription plans. When an API doesn't, its overview page shows no **Subscription plans** panel and no **Subscribe** button, and there's nothing to subscribe to—call it with whatever authentication its specification requires. -To try an API before wiring it into your code, use the **Try It** console on the [documentation page](discover-apis/api-documentations.md) rather than looking for a subscription. +To try an API before wiring it into your code, use the **Try It** console on the [documentation page](discover-apis/api-search.md#read-the-specification-and-try-it) rather than looking for a subscription. ## Subscription plans @@ -85,7 +85,7 @@ Plans set how much of an API you can consume. The API publisher decides which pl ## Related - [Consume an API](consume-an-api/overview.md): which credentials an API expects, and how they combine -- [API Overview](discover-apis/api-overview.md): where the subscription plans panel lives +- [API Overview](discover-apis/api-search.md#open-an-api): where the subscription plans panel lives - [Manage API Keys](manage-api-keys.md): generate an API key for a subscribed API - [Subscription Plans](admin-settings/subscription-plans.md): admin guide for defining plans - [Webhook Event Catalog](references/webhook-event-catalog.md): the `subscription.*` events subscribing, switching, and unsubscribing publish diff --git a/en/docs/next/api-portal/mcp-servers/discover-mcp-servers.md b/en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md similarity index 93% rename from en/docs/next/api-portal/mcp-servers/discover-mcp-servers.md rename to en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md index f8e552d59..4784e4421 100644 --- a/en/docs/next/api-portal/mcp-servers/discover-mcp-servers.md +++ b/en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md @@ -1,5 +1,5 @@ --- -title: "Discover MCP servers in the API Portal & MCP Hub" +title: "Browse MCP servers in the API Portal & MCP Hub" description: "Browse and search the MCP catalog, read a server's tools, resources, and prompts, and invoke them from the MCP Playground." canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/discover-mcp-servers/ md_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/discover-mcp-servers.md @@ -12,7 +12,7 @@ last_updated: 2026-07-31 content_type: "how-to" --- -# Discover MCP servers +# Browse MCP servers MCP servers have their own catalog, separate from the API listing but laid out the same way. @@ -31,7 +31,7 @@ Click a card to open the server. ## Search -Type a term into the search bar and press Enter. Search works exactly as it does for APIs—one free-text term matched against the server's metadata and tags, and on PostgreSQL against its attached documents too. See [Search APIs](../discover-apis/api-search.md#what-a-search-term-matches) for the details and the per-database differences. +Type a term into the search bar and press Enter. Search works exactly as it does for APIs—one free-text term matched against the server's metadata and tags, and on PostgreSQL against its attached documents too. See [Browse APIs](../discover-apis/api-search.md#what-a-search-term-matches) for the details and the per-database differences. Like the API listing, a catalog covers one [view](../admin-settings/manage-views.md), and a server appears only if one of its labels is mapped to that view. @@ -67,7 +67,7 @@ Click **Documentation** on a server to open its documentation page. The left pan The playground connects to the server's URL and lets you list and invoke its tools interactively. It expects a bearer token, so have one ready—see [Connect to an MCP Server](connect-to-an-mcp-server.md). -Attached documents render in the same pane, exactly as they do for an API. See [API Documentation](../discover-apis/api-documentations.md) for how that pane works. +Attached documents render in the same pane, exactly as they do for an API. See [API Documentation](../discover-apis/api-search.md#read-the-specification-and-try-it) for how that pane works. ## What agents see @@ -87,5 +87,5 @@ For programmatic discovery in the Model Context Protocol registry format, use th - [Connect to an MCP Server](connect-to-an-mcp-server.md): credentials and client configuration - [MCP Servers](overview.md): what an MCP server is and how it reaches the catalog -- [Search APIs](../discover-apis/api-search.md): how catalog search matches terms +- [Browse APIs](../discover-apis/api-search.md): how catalog search matches terms - [AI Agent Discovery](../discover-apis/ai-agent-discovery.md): the full set of agent-facing endpoints diff --git a/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md b/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md index 54a4ccd30..af22ff2c0 100644 --- a/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md +++ b/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md @@ -85,7 +85,7 @@ Before wiring the server into an agent, confirm the credentials work: open the s ## Related -- [Discover MCP Servers](discover-mcp-servers.md): find a server and read its tools +- [Browse MCP Servers](discover-mcp-servers.md): find a server and read its tools - [Manage Subscriptions](../manage-subscriptions.md): plans, tokens, switching, and unsubscribing - [Consume an API Secured with OAuth2](../consume-an-api/oauth2.md): the full token-generation sequence - [Manage Applications](../manage-applications.md): where client IDs live diff --git a/en/docs/next/api-portal/mcp-servers/mcp-registry.md b/en/docs/next/api-portal/mcp-servers/mcp-registry.md index 18b8a73fd..c85ce813b 100644 --- a/en/docs/next/api-portal/mcp-servers/mcp-registry.md +++ b/en/docs/next/api-portal/mcp-servers/mcp-registry.md @@ -217,6 +217,6 @@ Use the registry when you want MCP-standard tooling to interoperate with the hub ## Related - [MCP Servers](overview.md): how registry-published servers behave in the catalog -- [Discover MCP Servers](discover-mcp-servers.md): the same servers, in the portal UI +- [Browse MCP Servers](discover-mcp-servers.md): the same servers, in the portal UI - [MCP Servers (Management API)](../rest-api/mcp-servers.md): the portal's own CRUD API - [Authentication](../rest-api/authentication.md): how to get a bearer token for the write endpoints diff --git a/en/docs/next/api-portal/mcp-servers/overview.md b/en/docs/next/api-portal/mcp-servers/overview.md index a588b2f5e..084e990ae 100644 --- a/en/docs/next/api-portal/mcp-servers/overview.md +++ b/en/docs/next/api-portal/mcp-servers/overview.md @@ -56,7 +56,7 @@ See [MCP Registry API](mcp-registry.md). ## Where to go next -- [Discover MCP Servers](discover-mcp-servers.md): browse the catalog, read a server's tools, and try them in the playground +- [Browse MCP Servers](discover-mcp-servers.md): browse the catalog, read a server's tools, and try them in the playground - [Connect to an MCP Server](connect-to-an-mcp-server.md): subscribe, get credentials, and point an MCP client at the server - [MCP Registry API](mcp-registry.md): the discovery and publishing endpoints From 542fae29e2620fd025c1054dc5c0e5d8ef96fc2a Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake Date: Thu, 6 Aug 2026 14:29:15 +0530 Subject: [PATCH 05/11] Update file names --- en/docs/next/api-portal/about-this-release.md | 2 +- .../api-portal/admin-settings/api-content.md | 8 +- .../admin-settings/llm-instructions.md | 2 +- .../admin-settings/manage-api-workflows.md | 8 +- .../api-portal/admin-settings/manage-views.md | 2 +- en/docs/next/api-portal/api-workflows.md | 6 +- en/docs/next/api-portal/concepts.md | 2 +- .../next/api-portal/consume-an-api/api-key.md | 2 +- .../api-portal/consume-an-api/overview.md | 4 +- .../discover-apis/ai-agent-discovery.md | 6 +- .../api-portal/discover-apis/browse-apis.md | 4 +- .../next/api-portal/discover-apis/overview.md | 2 +- en/docs/next/api-portal/getting-started.md | 2 +- .../next/api-portal/manage-subscriptions.md | 4 +- .../mcp-servers/browse-mcp-servers.md | 10 +-- .../mcp-servers/connect-to-an-mcp-server.md | 2 +- .../api-portal/mcp-servers/mcp-registry.md | 2 +- .../next/api-portal/mcp-servers/overview.md | 2 +- en/docs/next/api-portal/overview.md | 4 +- en/mkdocs.yml | 76 ++++++++++--------- 20 files changed, 79 insertions(+), 71 deletions(-) diff --git a/en/docs/next/api-portal/about-this-release.md b/en/docs/next/api-portal/about-this-release.md index d5d191a5d..7a99cf182 100644 --- a/en/docs/next/api-portal/about-this-release.md +++ b/en/docs/next/api-portal/about-this-release.md @@ -42,7 +42,7 @@ To install and run it, follow the [Getting started](getting-started.md) guide. - **Labels**: Tag and filter artifacts across views. - **Per-artifact documentation**: Publish specifications, guides, and custom content next to each entry. - **[Learn more](discover-apis/api-search.md)** + **[Learn more](discover-apis/browse-apis.md)** ??? note "API workflows" diff --git a/en/docs/next/api-portal/admin-settings/api-content.md b/en/docs/next/api-portal/admin-settings/api-content.md index 8c81cbedb..8a4adab58 100644 --- a/en/docs/next/api-portal/admin-settings/api-content.md +++ b/en/docs/next/api-portal/admin-settings/api-content.md @@ -14,7 +14,7 @@ content_type: "how-to" # Customize an API's content -By default, an API's [overview page](../discover-apis/api-search.md#open-an-api) is generated from its specification—Endpoints, Resources, Scopes, and the subscription plans panel. **API content** lets you replace that body with your own, per API, and attach the images and documents that go with it. +By default, an API's [overview page](../discover-apis/browse-apis.md#open-an-api) is generated from its specification—Endpoints, Resources, Scopes, and the subscription plans panel. **API content** lets you replace that body with your own, per API, and attach the images and documents that go with it. This is narrower than [theming](../theming.md): a theme restyles every page in a view, while API content changes one API's overview body and leaves everything else alone. @@ -67,7 +67,7 @@ Without it, the portal falls back to a generated avatar showing the first two le ## Attach documents -Files under `docs/` become the documents listed on the API's [documentation page](../discover-apis/api-search.md#read-the-specification-and-try-it), and are served to AI agents as raw Markdown. +Files under `docs/` become the documents listed on the API's [documentation page](../discover-apis/browse-apis.md#read-the-specification-and-try-it), and are served to AI agents as raw Markdown. The **first-level directory name becomes the section heading** in the documentation navigation. Files placed directly in `docs/` land in a section called **Other**: @@ -114,8 +114,8 @@ samples/apis/my-api-v1.0/ ## Related -- [API Overview](../discover-apis/api-search.md#open-an-api): the generated page your content replaces -- [API Documentation](../discover-apis/api-search.md#read-the-specification-and-try-it): where `docs/` files surface +- [API Overview](../discover-apis/browse-apis.md#open-an-api): the generated page your content replaces +- [API Documentation](../discover-apis/browse-apis.md#read-the-specification-and-try-it): where `docs/` files surface - [Theming](../theming.md): restyle every page in a view, rather than one API's body - [Manage APIs](manage-apis.md): the wizard this is the last step of - [API Content](../rest-api/api-content.md): upload, replace, and delete content through the Management API diff --git a/en/docs/next/api-portal/admin-settings/llm-instructions.md b/en/docs/next/api-portal/admin-settings/llm-instructions.md index 3f48494dc..774bda10a 100644 --- a/en/docs/next/api-portal/admin-settings/llm-instructions.md +++ b/en/docs/next/api-portal/admin-settings/llm-instructions.md @@ -74,6 +74,6 @@ Only your name and description are editable. Everything below them—the API Wor ## Related - [AI Agent Discovery](../discover-apis/ai-agent-discovery.md): every agent-facing endpoint, and what `llms.txt` contains -- [Managing API Workflows](managing-api-workflows.md): per-workflow agent visibility +- [Managing API Workflows](manage-api-workflows.md): per-workflow agent visibility - [Make an API AI-Ready](../../../cloud/develop-api-proxy/make-api-ai-ready.md): per-API agent visibility, and writing descriptions agents can use - [Manage Views](manage-views.md): why these settings are per-view diff --git a/en/docs/next/api-portal/admin-settings/manage-api-workflows.md b/en/docs/next/api-portal/admin-settings/manage-api-workflows.md index 0c78813e7..979121a9e 100644 --- a/en/docs/next/api-portal/admin-settings/manage-api-workflows.md +++ b/en/docs/next/api-portal/admin-settings/manage-api-workflows.md @@ -1,8 +1,8 @@ --- -title: "Managing API workflows in the API Portal & MCP Hub" +title: "Manage API workflows in the API Portal & MCP Hub" description: "Author an API workflow as an Arazzo spec or Markdown, generate its agent prompt, and publish it to a view." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/managing-api-workflows/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/managing-api-workflows.md +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/manage-api-workflows/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/manage-api-workflows.md tags: - cloud - api-portal @@ -12,7 +12,7 @@ last_updated: 2026-07-31 content_type: "how-to" --- -# Managing API workflows +# Manage API workflows As a portal admin, you author API workflows, generate the prompt that lets AI agents execute them, and publish them to a view. This page covers the admin side; for what consumers and agents then see, read [API Workflows](../api-workflows.md). diff --git a/en/docs/next/api-portal/admin-settings/manage-views.md b/en/docs/next/api-portal/admin-settings/manage-views.md index 566a23285..ce8a79700 100644 --- a/en/docs/next/api-portal/admin-settings/manage-views.md +++ b/en/docs/next/api-portal/admin-settings/manage-views.md @@ -14,7 +14,7 @@ content_type: "how-to" # Manage views -A **view** is a filtered, branded subset of APIs—for example, `public` for external developers and `internal` for internal teams. Each view has its own URL (`/api-portal//views/`) and shows only the APIs tagged with its assigned labels. [LLM Instructions](llm-instructions.md) and [API Workflows](managing-api-workflows.md) are also configured per view. +A **view** is a filtered, branded subset of APIs—for example, `public` for external developers and `internal` for internal teams. Each view has its own URL (`/api-portal//views/`) and shows only the APIs tagged with its assigned labels. [LLM Instructions](llm-instructions.md) and [API Workflows](manage-api-workflows.md) are also configured per view. ## Adding a view diff --git a/en/docs/next/api-portal/api-workflows.md b/en/docs/next/api-portal/api-workflows.md index c64af4e78..dd1cac1c2 100644 --- a/en/docs/next/api-portal/api-workflows.md +++ b/en/docs/next/api-portal/api-workflows.md @@ -31,7 +31,7 @@ Each card carries: Click a card to open the workflow. !!! note - The gallery lists only workflows an admin has published; drafts appear nowhere. Admins also get a button into the authoring page—**Manage workflows** in the header, or **Create workflow** when the gallery is empty. See [Managing API Workflows](admin-settings/managing-api-workflows.md). + The gallery lists only workflows an admin has published; drafts appear nowhere. Admins also get a button into the authoring page—**Manage workflows** in the header, or **Create workflow** when the gallery is empty. See [Managing API Workflows](admin-settings/manage-api-workflows.md). ## Read a workflow @@ -98,13 +98,13 @@ Two independent settings decide who sees a workflow: | Status | Draft, Published | Draft workflows appear nowhere. Publishing puts a workflow in the gallery for the view it belongs to | | Agent visibility | Visible, Hidden | A hidden workflow stays in the gallery for people, but drops out of `llms.txt`, `api-workflows.md`, and every per-workflow endpoint, and loses its **Try with AI** button | -Hiding a workflow from agents is how you publish it for human developers while it's still being validated for automated execution. Admins set both, per workflow—see [Managing API Workflows](admin-settings/managing-api-workflows.md). +Hiding a workflow from agents is how you publish it for human developers while it's still being validated for automated execution. Admins set both, per workflow—see [Managing API Workflows](admin-settings/manage-api-workflows.md). Turning off **Portal is AI-discoverable** under [LLM Instructions](admin-settings/llm-instructions.md) makes every machine-readable workflow endpoint return `404`, whatever each workflow's own setting says. ## Related -- [Managing API Workflows](admin-settings/managing-api-workflows.md): author, publish, and set the visibility of workflows +- [Managing API Workflows](admin-settings/manage-api-workflows.md): author, publish, and set the visibility of workflows - [AI Agent Discovery](discover-apis/ai-agent-discovery.md): the portal's full set of agent-facing endpoints - [Consume an API](consume-an-api/overview.md): the credentials you'll need for the APIs a workflow calls - [Artifact types](artifact-types.md): whether this deployment serves API workflows at all diff --git a/en/docs/next/api-portal/concepts.md b/en/docs/next/api-portal/concepts.md index 11d1435e3..667df4047 100644 --- a/en/docs/next/api-portal/concepts.md +++ b/en/docs/next/api-portal/concepts.md @@ -74,7 +74,7 @@ An **MCP server** is a Model Context Protocol server published in the portal alo ## API workflow -An **API workflow** is a published, multi-step sequence of API calls defined in [Arazzo format](https://spec.openapis.org/arazzo/latest.html). Workflows are authored by admins and published per view for both human developers and AI agents to discover and follow. See [Managing API Workflows](admin-settings/managing-api-workflows.md) and [API Workflows](api-workflows.md). +An **API workflow** is a published, multi-step sequence of API calls defined in [Arazzo format](https://spec.openapis.org/arazzo/latest.html). Workflows are authored by admins and published per view for both human developers and AI agents to discover and follow. See [Managing API Workflows](admin-settings/manage-api-workflows.md) and [API Workflows](api-workflows.md). ## Subscription plan diff --git a/en/docs/next/api-portal/consume-an-api/api-key.md b/en/docs/next/api-portal/consume-an-api/api-key.md index 133f35f28..3e91071f7 100644 --- a/en/docs/next/api-portal/consume-an-api/api-key.md +++ b/en/docs/next/api-portal/consume-an-api/api-key.md @@ -54,7 +54,7 @@ The two answer different questions: the key says who you are, and the subscripti ## Check it before writing code -Open the API's [documentation page](../discover-apis/api-search.md#read-the-specification-and-try-it) and use the **Try It** console on a REST API, or **Tryout** on a GraphQL API, which has a dedicated **API Key** tab with fields for the header name and value. A call that succeeds there confirms both the header names and the key itself. +Open the API's [documentation page](../discover-apis/browse-apis.md#read-the-specification-and-try-it) and use the **Try It** console on a REST API, or **Tryout** on a GraphQL API, which has a dedicated **API Key** tab with fields for the header name and value. A call that succeeds there confirms both the header names and the key itself. ## Related diff --git a/en/docs/next/api-portal/consume-an-api/overview.md b/en/docs/next/api-portal/consume-an-api/overview.md index 00e8e4724..8db0a092a 100644 --- a/en/docs/next/api-portal/consume-an-api/overview.md +++ b/en/docs/next/api-portal/consume-an-api/overview.md @@ -33,7 +33,7 @@ Read the API's own specification—it's authoritative, and the portal derives it 3. Read the `security` entries for what it *requires*. A root-level `security` block applies to every operation; an operation can override it with its own. A scheme that's defined but never referenced isn't required. 4. Check for a subscription-key parameter. When the specification declares one, the portal shows a subscription token after you subscribe, along with the header to send it in. -Two shortcuts on the API's [overview page](../discover-apis/api-search.md#open-an-api) tell you the same thing faster: +Two shortcuts on the API's [overview page](../discover-apis/browse-apis.md#open-an-api) tell you the same thing faster: - An **API Keys** button appears for REST, WebSocket, and WebSub APIs whose specification declares API key security. The portal offers no key generation in the UI for GraphQL, SOAP, or MCP artifacts, even when their specification declares an `apiKey` scheme. For those, use the Management API: [API Keys](../rest-api/api-keys.md) for GraphQL and SOAP APIs, and [MCP Server Keys](../rest-api/mcp-server-keys.md) for MCP servers. - A **Subscription plans** panel appears only for APIs with plans, which is what a subscription token comes from. @@ -58,7 +58,7 @@ This one needs the most setup, because the OAuth application lives in a key mana ## Try before you wire it up -For REST APIs, the **Try It** console on the [documentation page](../discover-apis/api-search.md#read-the-specification-and-try-it) sends real requests from your browser. Paste the same credentials you'd use from code—it's the fastest way to confirm you have the right header names before writing a client. +For REST APIs, the **Try It** console on the [documentation page](../discover-apis/browse-apis.md#read-the-specification-and-try-it) sends real requests from your browser. Paste the same credentials you'd use from code—it's the fastest way to confirm you have the right header names before writing a client. ## Related diff --git a/en/docs/next/api-portal/discover-apis/ai-agent-discovery.md b/en/docs/next/api-portal/discover-apis/ai-agent-discovery.md index b80f98156..9b8457f52 100644 --- a/en/docs/next/api-portal/discover-apis/ai-agent-discovery.md +++ b/en/docs/next/api-portal/discover-apis/ai-agent-discovery.md @@ -112,20 +112,20 @@ A typical agent discovery flow looks like this: 5. **Fetch the specification separately.** When the agent needs the specification as a parseable file rather than as inlined text, it retrieves the `specification.*` endpoint for the API's type. 6. **Follow a workflow.** If a published workflow matches the task, the agent retrieves the Arazzo specification and agent prompt, then follows a vetted, step-by-step call sequence instead of reasoning from scratch. -The **Try with AI** button on an [API's overview page](api-search.md#open-an-api) hands this flow to an agent directly. It produces a prompt that points the agent at that API's `.md` URL and asks it to summarize the API before doing anything else. +The **Try with AI** button on an [API's overview page](browse-apis.md#open-an-api) hands this flow to an agent directly. It produces a prompt that points the agent at that API's `.md` URL and asks it to summarize the API before doing anything else. ## Visibility controls Three separate controls decide what agents see: -- **Per-artifact agent visibility.** All published APIs, MCP servers, and workflows are agent-visible by default. Setting an artifact to hidden removes it from `llms.txt`, the catalogs, and every Markdown and specification endpoint, while leaving it visible to human users in the portal. For APIs, see [Make an API AI-Ready](../../../cloud/develop-api-proxy/make-api-ai-ready.md). For workflows, see [Managing API Workflows](../admin-settings/managing-api-workflows.md). +- **Per-artifact agent visibility.** All published APIs, MCP servers, and workflows are agent-visible by default. Setting an artifact to hidden removes it from `llms.txt`, the catalogs, and every Markdown and specification endpoint, while leaving it visible to human users in the portal. For APIs, see [Make an API AI-Ready](../../../cloud/develop-api-proxy/make-api-ai-ready.md). For workflows, see [Managing API Workflows](../admin-settings/manage-api-workflows.md). - **Portal-wide AI discoverability.** Turning off **Portal is AI-discoverable** under [LLM Instructions](../admin-settings/llm-instructions.md) makes every endpoint on this page return `404`, including `llms.txt` itself. - **Served artifact types.** A deployment that doesn't serve a given artifact type returns `404` for that type's catalog, Markdown, and specification endpoints. See [Artifact types](../artifact-types.md). ## Related - [LLM Instructions](../admin-settings/llm-instructions.md): set the portal name and description at the top of `llms.txt`, and toggle AI discoverability -- [Managing API Workflows](../admin-settings/managing-api-workflows.md): publish workflows that guide agents through common multi-step use cases +- [Managing API Workflows](../admin-settings/manage-api-workflows.md): publish workflows that guide agents through common multi-step use cases - [API Workflows](../api-workflows.md): how agents discover and follow published workflows - [Make an API AI-Ready](../../../cloud/develop-api-proxy/make-api-ai-ready.md): publisher guidance on descriptions, specifications, and visibility settings - [MCP Servers](../mcp-servers/overview.md): how MCP servers are published, discovered, and connected to diff --git a/en/docs/next/api-portal/discover-apis/browse-apis.md b/en/docs/next/api-portal/discover-apis/browse-apis.md index 88d13a6c7..5452734c3 100644 --- a/en/docs/next/api-portal/discover-apis/browse-apis.md +++ b/en/docs/next/api-portal/discover-apis/browse-apis.md @@ -1,8 +1,8 @@ --- title: "Browse APIs in the API Portal & MCP Hub" description: "Browse and search published APIs in the API Portal & MCP Hub by name, version, type, description, tags, or documentation content." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/api-search/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/api-search.md +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/browse-apis/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/browse-apis.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/discover-apis/overview.md b/en/docs/next/api-portal/discover-apis/overview.md index b2c6db534..6ef270e51 100644 --- a/en/docs/next/api-portal/discover-apis/overview.md +++ b/en/docs/next/api-portal/discover-apis/overview.md @@ -65,7 +65,7 @@ The same artifact can be created programmatically, which is the route automation ## Where to go next -- [Browse APIs](api-search.md): search the catalog, open an API, and read its specification +- [Browse APIs](browse-apis.md): search the catalog, open an API, and read its specification - [Which Credentials You Need](../consume-an-api/overview.md): work out what an API expects before you call it ## Related diff --git a/en/docs/next/api-portal/getting-started.md b/en/docs/next/api-portal/getting-started.md index f590d514d..ffef51c06 100644 --- a/en/docs/next/api-portal/getting-started.md +++ b/en/docs/next/api-portal/getting-started.md @@ -244,7 +244,7 @@ Refresh the portal—the **Books API** now appears in the catalog. Select the ** ## What's next -- [Browse APIs](discover-apis/api-search.md): browse and search the catalog +- [Browse APIs](discover-apis/browse-apis.md): browse and search the catalog - [MCP Servers](mcp-servers/overview.md): publish and connect to Model Context Protocol servers - [AI Agent Discovery](discover-apis/ai-agent-discovery.md): the `llms.txt` and Markdown endpoints agents use - [Manage Applications](manage-applications.md): set up a container for OAuth2 credentials diff --git a/en/docs/next/api-portal/manage-subscriptions.md b/en/docs/next/api-portal/manage-subscriptions.md index 9b741791b..4d8912ad7 100644 --- a/en/docs/next/api-portal/manage-subscriptions.md +++ b/en/docs/next/api-portal/manage-subscriptions.md @@ -76,7 +76,7 @@ Cancelling invalidates the subscription token. Anything calling the API with it Not every API has subscription plans. When an API doesn't, its overview page shows no **Subscription plans** panel and no **Subscribe** button, and there's nothing to subscribe to—call it with whatever authentication its specification requires. -To try an API before wiring it into your code, use the **Try It** console on the [documentation page](discover-apis/api-search.md#read-the-specification-and-try-it) rather than looking for a subscription. +To try an API before wiring it into your code, use the **Try It** console on the [documentation page](discover-apis/browse-apis.md#read-the-specification-and-try-it) rather than looking for a subscription. ## Subscription plans @@ -85,7 +85,7 @@ Plans set how much of an API you can consume. The API publisher decides which pl ## Related - [Consume an API](consume-an-api/overview.md): which credentials an API expects, and how they combine -- [API Overview](discover-apis/api-search.md#open-an-api): where the subscription plans panel lives +- [API Overview](discover-apis/browse-apis.md#open-an-api): where the subscription plans panel lives - [Manage API Keys](manage-api-keys.md): generate an API key for a subscribed API - [Subscription Plans](admin-settings/subscription-plans.md): admin guide for defining plans - [Webhook Event Catalog](references/webhook-event-catalog.md): the `subscription.*` events subscribing, switching, and unsubscribing publish diff --git a/en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md b/en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md index 4784e4421..0bdc69cb8 100644 --- a/en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md +++ b/en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md @@ -1,8 +1,8 @@ --- title: "Browse MCP servers in the API Portal & MCP Hub" description: "Browse and search the MCP catalog, read a server's tools, resources, and prompts, and invoke them from the MCP Playground." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/discover-mcp-servers/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/discover-mcp-servers.md +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/browse-mcp-servers/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/browse-mcp-servers.md tags: - cloud - api-portal @@ -31,7 +31,7 @@ Click a card to open the server. ## Search -Type a term into the search bar and press Enter. Search works exactly as it does for APIs—one free-text term matched against the server's metadata and tags, and on PostgreSQL against its attached documents too. See [Browse APIs](../discover-apis/api-search.md#what-a-search-term-matches) for the details and the per-database differences. +Type a term into the search bar and press Enter. Search works exactly as it does for APIs—one free-text term matched against the server's metadata and tags, and on PostgreSQL against its attached documents too. See [Browse APIs](../discover-apis/browse-apis.md#what-a-search-term-matches) for the details and the per-database differences. Like the API listing, a catalog covers one [view](../admin-settings/manage-views.md), and a server appears only if one of its labels is mapped to that view. @@ -67,7 +67,7 @@ Click **Documentation** on a server to open its documentation page. The left pan The playground connects to the server's URL and lets you list and invoke its tools interactively. It expects a bearer token, so have one ready—see [Connect to an MCP Server](connect-to-an-mcp-server.md). -Attached documents render in the same pane, exactly as they do for an API. See [API Documentation](../discover-apis/api-search.md#read-the-specification-and-try-it) for how that pane works. +Attached documents render in the same pane, exactly as they do for an API. See [API Documentation](../discover-apis/browse-apis.md#read-the-specification-and-try-it) for how that pane works. ## What agents see @@ -87,5 +87,5 @@ For programmatic discovery in the Model Context Protocol registry format, use th - [Connect to an MCP Server](connect-to-an-mcp-server.md): credentials and client configuration - [MCP Servers](overview.md): what an MCP server is and how it reaches the catalog -- [Browse APIs](../discover-apis/api-search.md): how catalog search matches terms +- [Browse APIs](../discover-apis/browse-apis.md): how catalog search matches terms - [AI Agent Discovery](../discover-apis/ai-agent-discovery.md): the full set of agent-facing endpoints diff --git a/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md b/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md index af22ff2c0..d858c0c6c 100644 --- a/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md +++ b/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md @@ -85,7 +85,7 @@ Before wiring the server into an agent, confirm the credentials work: open the s ## Related -- [Browse MCP Servers](discover-mcp-servers.md): find a server and read its tools +- [Browse MCP Servers](browse-mcp-servers.md): find a server and read its tools - [Manage Subscriptions](../manage-subscriptions.md): plans, tokens, switching, and unsubscribing - [Consume an API Secured with OAuth2](../consume-an-api/oauth2.md): the full token-generation sequence - [Manage Applications](../manage-applications.md): where client IDs live diff --git a/en/docs/next/api-portal/mcp-servers/mcp-registry.md b/en/docs/next/api-portal/mcp-servers/mcp-registry.md index c85ce813b..823fdb7a0 100644 --- a/en/docs/next/api-portal/mcp-servers/mcp-registry.md +++ b/en/docs/next/api-portal/mcp-servers/mcp-registry.md @@ -217,6 +217,6 @@ Use the registry when you want MCP-standard tooling to interoperate with the hub ## Related - [MCP Servers](overview.md): how registry-published servers behave in the catalog -- [Browse MCP Servers](discover-mcp-servers.md): the same servers, in the portal UI +- [Browse MCP Servers](browse-mcp-servers.md): the same servers, in the portal UI - [MCP Servers (Management API)](../rest-api/mcp-servers.md): the portal's own CRUD API - [Authentication](../rest-api/authentication.md): how to get a bearer token for the write endpoints diff --git a/en/docs/next/api-portal/mcp-servers/overview.md b/en/docs/next/api-portal/mcp-servers/overview.md index 084e990ae..5f9e7784e 100644 --- a/en/docs/next/api-portal/mcp-servers/overview.md +++ b/en/docs/next/api-portal/mcp-servers/overview.md @@ -56,7 +56,7 @@ See [MCP Registry API](mcp-registry.md). ## Where to go next -- [Browse MCP Servers](discover-mcp-servers.md): browse the catalog, read a server's tools, and try them in the playground +- [Browse MCP Servers](browse-mcp-servers.md): browse the catalog, read a server's tools, and try them in the playground - [Connect to an MCP Server](connect-to-an-mcp-server.md): subscribe, get credentials, and point an MCP client at the server - [MCP Registry API](mcp-registry.md): the discovery and publishing endpoints diff --git a/en/docs/next/api-portal/overview.md b/en/docs/next/api-portal/overview.md index e776ec7ca..2da627388 100644 --- a/en/docs/next/api-portal/overview.md +++ b/en/docs/next/api-portal/overview.md @@ -30,7 +30,7 @@ Browse and search REST, GraphQL, SOAP, WebSocket, and WebSub APIs, as well as MC - Per-API documentation, landing page content, icons, and banners - Machine-readable discovery (`llms.txt`, per-API Markdown, OpenAPI/AsyncAPI specs) built for AI agent consumption -Learn more in [Discover APIs](discover-apis/api-search.md), [MCP Servers](mcp-servers/overview.md), and [AI Agent Discovery](discover-apis/ai-agent-discovery.md). +Learn more in [Discover APIs](discover-apis/browse-apis.md), [MCP Servers](mcp-servers/overview.md), and [AI Agent Discovery](discover-apis/ai-agent-discovery.md). ### MCP servers @@ -83,7 +83,7 @@ The **Settings** page is where admins configure everything above from one place: | [Manage MCP Servers](admin-settings/manage-mcp-servers.md) | Add, edit, publish, deprecate, and delete MCP servers | | [Webhook Integration](admin-settings/webhook-integration.md) | Endpoints notified when API keys or subscriptions change | | [LLM Instructions](admin-settings/llm-instructions.md) | Portal-level context published to `llms.txt` for AI agents | -| [Managing API Workflows](admin-settings/managing-api-workflows.md) | Author, publish, and control visibility of API workflows | +| [Managing API Workflows](admin-settings/manage-api-workflows.md) | Author, publish, and control visibility of API workflows | | [Apply a Theme](admin-settings/theming.md) | Upload, download, and reset a view's custom theme | ## Gateway-agnostic, unified developer experience diff --git a/en/mkdocs.yml b/en/mkdocs.yml index 65aa15e57..611e1dbd4 100644 --- a/en/mkdocs.yml +++ b/en/mkdocs.yml @@ -316,7 +316,7 @@ nav: - Devportal Mode: cloud/devportal/developer-portal-mode.md - AI Agent Discovery: cloud/devportal/discover-apis/ai-agent-discovery.md - Discover APIs: - - Search APIs: cloud/devportal/discover-apis/api-search.md + - Search APIs: cloud/devportal/discover-apis/browse-apis.md - Documentations: cloud/devportal/discover-apis/api-documentations.md - API Workflows: - Consuming API Workflows: cloud/devportal/api-workflows/consuming-api-workflows.md @@ -331,7 +331,7 @@ nav: - AI Assisted SDK Generation: cloud/devportal/sdk-generation/ai-assisted-sdk-generation.md - Admin Settings: - LLM Instructions: cloud/devportal/admin-settings/llm-instructions.md - - Managing API Workflows: cloud/devportal/admin-settings/managing-api-workflows.md + - Managing API Workflows: cloud/devportal/admin-settings/manage-api-workflows.md - API Manager: - Overview: api-manager/overview.md - Documentation: https://apim.docs.wso2.com/en/latest/ @@ -689,27 +689,9 @@ nav: - API Portal & MCP Hub: - Overview: next/api-portal/overview.md - About this Release: next/api-portal/about-this-release.md - - Getting Started: next/api-portal/getting-started.md - Concepts: next/api-portal/concepts.md - - Discover APIs: - - Search APIs: next/api-portal/discover-apis/api-search.md - - API Overview: next/api-portal/discover-apis/api-overview.md - - API Documentation: next/api-portal/discover-apis/api-documentations.md - - AI Agent Discovery: next/api-portal/discover-apis/ai-agent-discovery.md - - MCP Servers: - - Overview: next/api-portal/mcp-servers/overview.md - - Discover MCP Servers: next/api-portal/mcp-servers/discover-mcp-servers.md - - Connect to an MCP Server: next/api-portal/mcp-servers/connect-to-an-mcp-server.md - - MCP Registry API: next/api-portal/mcp-servers/mcp-registry.md - - API Workflows: next/api-portal/api-workflows.md - - Consume an API: - - Overview: next/api-portal/consume-an-api/overview.md - - OAuth2-Secured APIs: next/api-portal/consume-an-api/oauth2.md - - API-Key-Secured APIs: next/api-portal/consume-an-api/api-key.md - - Manage Applications: next/api-portal/manage-applications.md - - Manage Subscriptions: next/api-portal/manage-subscriptions.md - - Manage API Keys: next/api-portal/manage-api-keys.md - - Theming: next/api-portal/theming.md + # Stand the portal up, in the order an operator does it. + - Getting Started: next/api-portal/getting-started.md - Tutorials: - Secured API End to End: next/api-portal/tutorials/secured-api-end-to-end.md - Setting Up: @@ -717,29 +699,48 @@ nav: - Ports: next/api-portal/setting-up/ports.md - Database: next/api-portal/setting-up/database.md - Authentication: - - Overview: next/api-portal/setting-up/authentication/overview.md - - Connect an Identity Provider: next/api-portal/setting-up/authentication/connect-an-identity-provider.md - - Set up Asgardeo: next/api-portal/tutorials/asgardeo-as-idp.md - - Artifact types: next/api-portal/artifact-types.md + - Overview: next/api-portal/setting-up/authentication/overview.md + - Connect an Identity Provider: next/api-portal/setting-up/authentication/connect-an-identity-provider.md + - Set up Asgardeo: next/api-portal/tutorials/asgardeo-as-idp.md + - Artifact Types: next/api-portal/artifact-types.md - Design Mode: next/api-portal/setting-up/design-mode.md + # What a consumer does in the portal, in the order they do it. + - Discover APIs: + - Overview: next/api-portal/discover-apis/overview.md + - Browse APIs: next/api-portal/discover-apis/browse-apis.md + - Discover MCP Servers: + - Overview: next/api-portal/mcp-servers/overview.md + - Browse MCP Servers: next/api-portal/mcp-servers/browse-mcp-servers.md + - Connect to an MCP Server: next/api-portal/mcp-servers/connect-to-an-mcp-server.md + - API Workflows: next/api-portal/api-workflows.md + - AI Agent Discovery: next/api-portal/discover-apis/ai-agent-discovery.md + - MCP Registry API: next/api-portal/mcp-servers/mcp-registry.md + - Applications and Credentials: + - Which Credentials You Need: next/api-portal/consume-an-api/overview.md + - Manage Applications: next/api-portal/manage-applications.md + - Manage Subscriptions: next/api-portal/manage-subscriptions.md + - Manage API Keys: next/api-portal/manage-api-keys.md + - Call an OAuth2-Secured API: next/api-portal/consume-an-api/oauth2.md + - Call an API-Key-Secured API: next/api-portal/consume-an-api/api-key.md + # Configure the portal at runtime, once it is serving. - Admin Settings: - Organization: - Organization Settings: next/api-portal/admin-settings/organization-settings.md - Manage Views: next/api-portal/admin-settings/manage-views.md - Manage Labels: next/api-portal/admin-settings/manage-labels.md - Subscription Plans: next/api-portal/admin-settings/subscription-plans.md - - Key Manager Integration: next/api-portal/admin-settings/key-manager-integration.md - Content: - Manage APIs: next/api-portal/admin-settings/manage-apis.md - Manage MCP Servers: next/api-portal/admin-settings/manage-mcp-servers.md + - Manage API Workflows: next/api-portal/admin-settings/manage-api-workflows.md - API Content: next/api-portal/admin-settings/api-content.md + - LLM Instructions: next/api-portal/admin-settings/llm-instructions.md - Integrations: + - Key Manager Integration: next/api-portal/admin-settings/key-manager-integration.md - Webhook Integration: next/api-portal/admin-settings/webhook-integration.md - - AI & Discovery: - - LLM Instructions: next/api-portal/admin-settings/llm-instructions.md - - Managing API Workflows: next/api-portal/admin-settings/managing-api-workflows.md - Appearance: - - Apply a Theme: next/api-portal/admin-settings/theming.md + - Theming: next/api-portal/theming.md + - Apply a Theme to a View: next/api-portal/admin-settings/theming.md - References: - Management API: - Overview: next/api-portal/rest-api/overview.md @@ -752,13 +753,13 @@ nav: - Key Managers: next/api-portal/rest-api/key-managers.md - APIs: next/api-portal/rest-api/apis.md - API Content: next/api-portal/rest-api/api-content.md + - API Keys: next/api-portal/rest-api/api-keys.md - MCP Servers: next/api-portal/rest-api/mcp-servers.md - MCP Server Content: next/api-portal/rest-api/mcp-server-content.md - MCP Server Keys: next/api-portal/rest-api/mcp-server-keys.md - Applications: next/api-portal/rest-api/applications.md - Application Keys: next/api-portal/rest-api/application-keys.md - Subscriptions: next/api-portal/rest-api/subscriptions.md - - API Keys: next/api-portal/rest-api/api-keys.md - API Workflows: next/api-portal/rest-api/api-workflows.md - Webhook Subscribers: next/api-portal/rest-api/webhook-subscribers.md - Webhook Events: next/api-portal/rest-api/webhook-events.md @@ -961,6 +962,13 @@ plugins: - markdownextradata: {} - redirects: redirect_maps: + # API Portal: overview + documentation pages merged into Browse APIs + next/api-portal/discover-apis/api-overview.md: next/api-portal/discover-apis/browse-apis.md + next/api-portal/discover-apis/api-documentations.md: next/api-portal/discover-apis/browse-apis.md + # API Portal: filenames aligned with page titles + next/api-portal/discover-apis/api-search.md: next/api-portal/discover-apis/browse-apis.md + next/api-portal/mcp-servers/discover-mcp-servers.md: next/api-portal/mcp-servers/browse-mcp-servers.md + next/api-portal/admin-settings/managing-api-workflows.md: next/api-portal/admin-settings/manage-api-workflows.md # AI/API Gateway versioning restructuring (per-product Cloud/Self-Hosted variants) ai-gateway/ai-gateway-rest-api/authentication.md: ai-gateway/1.1.0/ai-gateway-rest-api/authentication.md ai-gateway/analytics/analytics-header-filter.md: ai-gateway/1.1.0/analytics/analytics-header-filter.md @@ -1270,13 +1278,13 @@ plugins: # Developer Portal devportal/developer-portal-mode.md: cloud/devportal/developer-portal-mode.md devportal/admin-settings/llm-instructions.md: cloud/devportal/admin-settings/llm-instructions.md - devportal/admin-settings/managing-api-workflows.md: cloud/devportal/admin-settings/managing-api-workflows.md + devportal/admin-settings/manage-api-workflows.md: cloud/devportal/admin-settings/manage-api-workflows.md devportal/api-workflows/consuming-api-workflows.md: cloud/devportal/api-workflows/consuming-api-workflows.md devportal/consuming-services/consume-an-api-secured-with-api-key.md: cloud/devportal/consuming-services/consume-an-api-secured-with-api-key.md devportal/consuming-services/consume-an-api-secured-with-oauth2.md: cloud/devportal/consuming-services/consume-an-api-secured-with-oauth2.md devportal/discover-apis/ai-agent-discovery.md: cloud/devportal/discover-apis/ai-agent-discovery.md devportal/discover-apis/api-documentations.md: cloud/devportal/discover-apis/api-documentations.md - devportal/discover-apis/api-search.md: cloud/devportal/discover-apis/api-search.md + devportal/discover-apis/browse-apis.md: cloud/devportal/discover-apis/browse-apis.md devportal/manage-applications/create-an-application.md: cloud/devportal/manage-applications/create-an-application.md devportal/manage-subscriptions/subscribe-to-an-api.md: cloud/devportal/manage-subscriptions/subscribe-to-an-api.md # Event Gateway From e8fb0bd3fcb5797ec32ec066a93f52b0010f787a Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake Date: Fri, 7 Aug 2026 08:06:17 +0530 Subject: [PATCH 06/11] Update api portal version --- en/docs/next/api-portal/about-this-release.md | 4 ++-- en/docs/next/api-portal/getting-started.md | 4 ++-- en/docs/next/api-portal/tutorials/secured-api-end-to-end.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/en/docs/next/api-portal/about-this-release.md b/en/docs/next/api-portal/about-this-release.md index 7a99cf182..c4b8ed22e 100644 --- a/en/docs/next/api-portal/about-this-release.md +++ b/en/docs/next/api-portal/about-this-release.md @@ -25,8 +25,8 @@ For more information on the API Portal & MCP Hub, see the [overview](overview.md Download the standalone distribution from the WSO2 API Platform release page: ```bash -curl -sLO https://github.com/wso2/api-platform/releases/download/api-portal%2Fv1.0.0-rc3/wso2apip-api-portal-1.0.0-rc3.zip && \ -unzip wso2apip-api-portal-1.0.0-rc3.zip +curl -sLO https://github.com/wso2/api-platform/releases/download/api-portal%2Fv1.0.0/wso2apip-api-portal-1.0.0.zip && \ +unzip wso2apip-api-portal-1.0.0.zip ``` To install and run it, follow the [Getting started](getting-started.md) guide. diff --git a/en/docs/next/api-portal/getting-started.md b/en/docs/next/api-portal/getting-started.md index ffef51c06..a95cd69e7 100644 --- a/en/docs/next/api-portal/getting-started.md +++ b/en/docs/next/api-portal/getting-started.md @@ -28,8 +28,8 @@ The API Portal & MCP Hub is where developers discover, subscribe to, and consume Run this command in your terminal to download and unzip the standalone API Portal distribution: ```bash -curl -sLO https://github.com/wso2/api-platform/releases/download/api-portal%2Fv1.0.0-rc3/wso2apip-api-portal-1.0.0-rc3.zip && \ -unzip wso2apip-api-portal-1.0.0-rc3.zip +curl -sLO https://github.com/wso2/api-platform/releases/download/api-portal%2Fv1.0.0/wso2apip-api-portal-1.0.0.zip && \ +unzip wso2apip-api-portal-1.0.0.zip ``` ## Step 2: Run the setup script diff --git a/en/docs/next/api-portal/tutorials/secured-api-end-to-end.md b/en/docs/next/api-portal/tutorials/secured-api-end-to-end.md index 1832c0e7b..33bb93434 100644 --- a/en/docs/next/api-portal/tutorials/secured-api-end-to-end.md +++ b/en/docs/next/api-portal/tutorials/secured-api-end-to-end.md @@ -40,8 +40,8 @@ API Portal ──signed webhook──▶ Platform API ──control plane─ The API Portal distribution ships the Platform API alongside it, so one compose file gives you both. ```bash -curl -sLO https://github.com/wso2/api-platform/releases/download/api-portal%2Fv1.0.0-rc3/wso2apip-api-portal-1.0.0-rc3.zip -unzip wso2apip-api-portal-1.0.0-rc3.zip +curl -sLO https://github.com/wso2/api-platform/releases/download/api-portal%2Fv1.0.0/wso2apip-api-portal-1.0.0.zip +unzip wso2apip-api-portal-1.0.0.zip cd wso2apip-api-portal-1.0.0 ./scripts/setup.sh docker compose up -d From a8451b9719e2881b23fd085530cb324bc9e6f7c1 Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake Date: Fri, 7 Aug 2026 11:38:27 +0530 Subject: [PATCH 07/11] Refactor doc file names and paths --- en/docs/next/api-portal/about-this-release.md | 12 +- .../api-portal/admin-settings/api-content.md | 8 +- .../admin-settings/apply-a-theme.md | 54 +++++ .../design-mode.md | 12 +- .../admin-settings/llm-instructions.md | 4 +- .../admin-settings/manage-api-workflows.md | 4 +- .../admin-settings/manage-mcp-servers.md | 4 +- .../admin-settings/organization-settings.md | 2 +- .../next/api-portal/admin-settings/theming.md | 193 ++++++++++++++--- .../admin-settings/webhook-integration.md | 2 +- .../{discover-apis => }/ai-agent-discovery.md | 30 +-- en/docs/next/api-portal/api-workflows.md | 6 +- en/docs/next/api-portal/concepts.md | 10 +- .../next/api-portal/consume-an-api/api-key.md | 8 +- .../{ => consume-an-api}/manage-api-keys.md | 14 +- .../manage-applications.md | 12 +- .../manage-subscriptions.md | 18 +- .../next/api-portal/consume-an-api/oauth2.md | 8 +- .../api-portal/consume-an-api/overview.md | 12 +- .../api-portal/discover-apis/browse-apis.md | 6 +- .../next/api-portal/discover-apis/overview.md | 8 +- en/docs/next/api-portal/getting-started.md | 8 +- .../{mcp-servers => }/mcp-registry.md | 14 +- .../mcp-servers/browse-mcp-servers.md | 6 +- .../mcp-servers/connect-to-an-mcp-server.md | 8 +- .../next/api-portal/mcp-servers/overview.md | 12 +- en/docs/next/api-portal/overview.md | 16 +- .../api-portal/references/configurations.md | 10 +- .../references/webhook-event-catalog.md | 6 +- .../{ => setting-up}/artifact-types.md | 4 +- .../authentication}/asgardeo-as-idp.md | 20 +- .../connect-an-identity-provider.md | 4 +- .../setting-up/authentication/overview.md | 2 +- .../api-portal/setting-up/configuration.md | 4 +- en/docs/next/api-portal/theming.md | 197 ------------------ .../tutorials/secured-api-end-to-end.md | 4 +- en/mkdocs.yml | 30 ++- 37 files changed, 391 insertions(+), 381 deletions(-) create mode 100644 en/docs/next/api-portal/admin-settings/apply-a-theme.md rename en/docs/next/api-portal/{setting-up => admin-settings}/design-mode.md (92%) rename en/docs/next/api-portal/{discover-apis => }/ai-agent-discovery.md (75%) rename en/docs/next/api-portal/{ => consume-an-api}/manage-api-keys.md (88%) rename en/docs/next/api-portal/{ => consume-an-api}/manage-applications.md (89%) rename en/docs/next/api-portal/{ => consume-an-api}/manage-subscriptions.md (82%) rename en/docs/next/api-portal/{mcp-servers => }/mcp-registry.md (94%) rename en/docs/next/api-portal/{ => setting-up}/artifact-types.md (95%) rename en/docs/next/api-portal/{tutorials => setting-up/authentication}/asgardeo-as-idp.md (91%) delete mode 100644 en/docs/next/api-portal/theming.md diff --git a/en/docs/next/api-portal/about-this-release.md b/en/docs/next/api-portal/about-this-release.md index c4b8ed22e..fea51d8db 100644 --- a/en/docs/next/api-portal/about-this-release.md +++ b/en/docs/next/api-portal/about-this-release.md @@ -14,7 +14,7 @@ content_type: "release-notes" # About this release -The API Portal & MCP Hub is a web application that serves a catalog of APIs, Model Context Protocol (MCP) servers, and API workflows. Publishers register artifacts in it, and developers browse them, subscribe to plans, generate credentials, and call the APIs. It runs as a standalone distribution alongside the Platform API control plane, keeps its own database, and reaches gateways through signed webhook events rather than a fixed control-plane binding. +The API Portal & MCP Hub is a web application that serves a catalog of APIs, Model Context Protocol (MCP) servers, and API workflows. Publishers register artifacts in it, and developers browse them, subscribe to plans, generate credentials, and call the APIs. It is a standalone product: it runs as its own distribution, keeps its own database, authenticates against your identity provider, and reaches gateways through signed webhook events rather than a fixed control-plane binding. **API Portal & MCP Hub 1.0.0** is the first **API Portal & MCP Hub release**. Every capability listed below is available for the first time, so there is no predecessor to upgrade from. @@ -61,7 +61,7 @@ To install and run it, follow the [Getting started](getting-started.md) guide. - **Machine-readable discovery**: Query published MCP servers and their connection details over HTTP. - **Agent-oriented**: Intended for MCP clients and agent frameworks that resolve servers at runtime. - **[Learn more](mcp-servers/mcp-registry.md)** + **[Learn more](mcp-registry.md)** ??? note "AI agent discovery" @@ -70,7 +70,7 @@ To install and run it, follow the [Getting started](getting-started.md) guide. - **`llms.txt` entry points**: Machine-readable indexes of the APIs, MCP servers, and workflows a view serves. - **Administrator-authored guidance**: Supply instructions that shape how large language models (LLMs) interpret the catalog. - **[Learn more](discover-apis/ai-agent-discovery.md)** + **[Learn more](ai-agent-discovery.md)** ??? note "Applications, subscriptions, and API keys" @@ -102,7 +102,7 @@ To install and run it, follow the [Getting started](getting-started.md) guide. - **Per-view layouts**: Upload page layouts that apply to a single view. - **Per-API styling**: Apply custom styling to an individual API's landing page. - **[Learn more](theming.md)** + **[Learn more](admin-settings/theming.md)** ??? note "Design mode" @@ -111,7 +111,7 @@ To install and run it, follow the [Getting started](getting-started.md) guide. - **No infrastructure**: Preview APIs, MCP servers, applications, and theming without standing up the full stack. - **Content and theme authoring**: Iterate on layouts and catalog content directly from disk. - **[Learn more](setting-up/design-mode.md)** + **[Learn more](admin-settings/design-mode.md)** ??? note "Webhook-based event integration" @@ -145,7 +145,7 @@ The API Portal & MCP Hub shares a control plane with AI Workspace. The following |---------|--------------------| | WSO2 AI Workspace | 1.0.0 | -The distribution bundles the Platform API control plane, so the two are versioned and shipped together—no separate compatibility check is needed. Full prerequisites are listed in the [Getting started](getting-started.md) guide. +The distribution bundles the Platform API control plane so the local-auth quickstart works out of the box, and the two are versioned and shipped together—no separate compatibility check is needed. A production deployment that authenticates against an identity provider doesn't need it: the portal's only outbound call to a Platform API is the local-auth login. Full prerequisites are listed in the [Getting started](getting-started.md) guide. ## Key changes diff --git a/en/docs/next/api-portal/admin-settings/api-content.md b/en/docs/next/api-portal/admin-settings/api-content.md index 8a4adab58..dc34c6ca4 100644 --- a/en/docs/next/api-portal/admin-settings/api-content.md +++ b/en/docs/next/api-portal/admin-settings/api-content.md @@ -16,7 +16,7 @@ content_type: "how-to" By default, an API's [overview page](../discover-apis/browse-apis.md#open-an-api) is generated from its specification—Endpoints, Resources, Scopes, and the subscription plans panel. **API content** lets you replace that body with your own, per API, and attach the images and documents that go with it. -This is narrower than [theming](../theming.md): a theme restyles every page in a view, while API content changes one API's overview body and leaves everything else alone. +This is narrower than [theming](theming.md): a theme restyles every page in a view, while API content changes one API's overview body and leaves everything else alone. ## What you can upload @@ -95,11 +95,11 @@ Uploading is immediate and independent of **Save changes** on the other steps. Files are merged rather than replaced wholesale: a file whose name matches one already stored overwrites it, and everything else is added. To remove a file, delete it through the [API Content](../rest-api/api-content.md) Management API. !!! note "The wrapper directory is optional here" - A single top-level folder containing `web/` and `docs/` is fine, and so is `web/` and `docs/` sitting at the ZIP root—the portal accepts both. This differs from a [theme ZIP](../theming.md#package-the-theme), which requires the wrapper. + A single top-level folder containing `web/` and `docs/` is fine, and so is `web/` and `docs/` sitting at the ZIP root—the portal accepts both. This differs from a [theme ZIP](theming.md#package-the-theme), which requires the wrapper. ## Preview before uploading -In [design mode](../setting-up/design-mode.md), an API's content lives beside its manifest in the samples directory, so you can iterate on the body with a browser reload: +In [design mode](design-mode.md), an API's content lives beside its manifest in the samples directory, so you can iterate on the body with a browser reload: ```text samples/apis/my-api-v1.0/ @@ -116,6 +116,6 @@ samples/apis/my-api-v1.0/ - [API Overview](../discover-apis/browse-apis.md#open-an-api): the generated page your content replaces - [API Documentation](../discover-apis/browse-apis.md#read-the-specification-and-try-it): where `docs/` files surface -- [Theming](../theming.md): restyle every page in a view, rather than one API's body +- [Theming](theming.md): restyle every page in a view, rather than one API's body - [Manage APIs](manage-apis.md): the wizard this is the last step of - [API Content](../rest-api/api-content.md): upload, replace, and delete content through the Management API diff --git a/en/docs/next/api-portal/admin-settings/apply-a-theme.md b/en/docs/next/api-portal/admin-settings/apply-a-theme.md new file mode 100644 index 000000000..fa66c0762 --- /dev/null +++ b/en/docs/next/api-portal/admin-settings/apply-a-theme.md @@ -0,0 +1,54 @@ +--- +title: "Apply a theme to a view" +description: "Upload a theme ZIP to a view in the API Portal & MCP Hub, download the current theme, or reset to the built-in default." +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/apply-a-theme/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/apply-a-theme.md +tags: + - cloud + - api-portal + - theming +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-23 +content_type: "how-to" +--- + +# Apply a theme + +The **Theming** tab in the API Portal's Settings page is where you upload a theme to a view, download what's currently applied, and roll back to the built-in default. + +This page covers the panel. To build the theme in the first place—which files you can override, how the color tokens work, and how to package the ZIP—see [Theming](theming.md). + +Theming is configured **per view**: if your organization has more than one [view](manage-views.md), use the view switcher at the top of the panel to pick which one you're theming. + +## Applying a custom theme + +1. Navigate to **Settings** and select the **Theming** tab under **APPEARANCE**. +2. Pick the view you want to theme, if you have more than one. +3. Under **Upload Theme**, drag and drop (or browse to) a single ZIP archive, up to 10 MB. + + The archive must hold **one wrapper directory** containing the theme's folders—`my-theme/styles/`, `my-theme/layout/`, `my-theme/partials/`, `my-theme/pages/`, `my-theme/images/`—not those folders at the archive root. See [Package the theme](theming.md#package-the-theme); getting this wrong makes the layout silently not apply. + +4. Click **Apply theme**. + +!!! warning "Uploading replaces the current theme entirely" + Applying a new ZIP replaces this view's existing custom theme files completely—it isn't merged with what was there before. Use **Download theme** first if you want a backup or a starting point to edit. + +The panel's **Current theme** indicator shows whether the view is using a custom uploaded theme or the built-in default. + +## Downloading the current theme + +Click **Download theme** to get a ZIP of what's currently applied to the view. If the view has no custom theme, this downloads the built-in default theme instead—so it always gives you a valid starting point to customize. + +## Resetting to the default theme + +Click **Reset to default** to discard the view's custom theme files and revert to the built-in default. This can't be undone—download a copy first if you might want it again. + +## API-level content + +Theming here controls the view-wide look and feel. To change one API's overview body, set its icon, or attach documents, see [Customize an API's Content](api-content.md). + +## Related + +- [Theming](theming.md): build a theme, and the sample you can start from +- [Design Mode](design-mode.md): preview a theme before uploading it +- [Manage Views](manage-views.md): themes are scoped per view diff --git a/en/docs/next/api-portal/setting-up/design-mode.md b/en/docs/next/api-portal/admin-settings/design-mode.md similarity index 92% rename from en/docs/next/api-portal/setting-up/design-mode.md rename to en/docs/next/api-portal/admin-settings/design-mode.md index ea817c800..380c4d0fe 100644 --- a/en/docs/next/api-portal/setting-up/design-mode.md +++ b/en/docs/next/api-portal/admin-settings/design-mode.md @@ -1,8 +1,8 @@ --- title: "Design Mode" description: "Develop and preview API Portal layouts and themes offline, without a running database or identity provider." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/design-mode/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/design-mode.md +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/design-mode/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/design-mode.md tags: - cloud - api-portal @@ -24,7 +24,7 @@ Design mode lets you develop and preview API page layouts and org-level themes w Use design mode when you want to: -- Iterate on a [theme](../theming.md)—styles, layout, partials, or page templates—and see each change on reload +- Iterate on a [theme](theming.md)—styles, layout, partials, or page templates—and see each change on reload - Work on a page design without standing up a database, an identity provider, or a gateway - Demo the portal against a fixed set of sample APIs, MCP servers, and applications @@ -81,9 +81,9 @@ enabled = true path_to_layout = "./my-theme/" ``` -To preview the example theme from [Theming](../theming.md#example-a-teal-and-coral-theme), use `./samples/layouts/green-theme/`. +To preview the example theme from [Theming](theming.md#example-a-teal-and-coral-theme), use `./samples/layouts/green-theme/`. -For what a theme contains, which files you can override, how the color tokens work, and how to package one for upload, see [Theming](../theming.md). Design mode and the production theme upload use the same directory structure, so a theme built here deploys without conversion. +For what a theme contains, which files you can override, how the color tokens work, and how to package one for upload, see [Theming](theming.md). Design mode and the production theme upload use the same directory structure, so a theme built here deploys without conversion. ## Sample APIs and MCP servers @@ -228,6 +228,6 @@ The portal returns to production mode, requiring a database and (if configured) ## Related -- [Theming](../theming.md): what a theme contains, the color tokens, and how to package one +- [Theming](theming.md): what a theme contains, the color tokens, and how to package one - [Apply a Theme](../admin-settings/theming.md): upload a finished theme to a view - [Configurations](../references/configurations.md): the full `config.toml` reference diff --git a/en/docs/next/api-portal/admin-settings/llm-instructions.md b/en/docs/next/api-portal/admin-settings/llm-instructions.md index 774bda10a..f9909b3ab 100644 --- a/en/docs/next/api-portal/admin-settings/llm-instructions.md +++ b/en/docs/next/api-portal/admin-settings/llm-instructions.md @@ -69,11 +69,11 @@ The generated file lists each agent-visible artifact under its own section: ![Generated llms.txt showing an API Workflows section with workflow names, descriptions, and links](../../../assets/img/devportal/llms-txt.png) -Only your name and description are editable. Everything below them—the API Workflows, APIs, MCPs, GraphQL, WebSocket, and WebSub sections—is generated from the catalog and reflects each artifact's own agent visibility. See [AI Agent Discovery](../discover-apis/ai-agent-discovery.md) for how that index is built. +Only your name and description are editable. Everything below them—the API Workflows, APIs, MCPs, GraphQL, WebSocket, and WebSub sections—is generated from the catalog and reflects each artifact's own agent visibility. See [AI Agent Discovery](../ai-agent-discovery.md) for how that index is built. ## Related -- [AI Agent Discovery](../discover-apis/ai-agent-discovery.md): every agent-facing endpoint, and what `llms.txt` contains +- [AI Agent Discovery](../ai-agent-discovery.md): every agent-facing endpoint, and what `llms.txt` contains - [Managing API Workflows](manage-api-workflows.md): per-workflow agent visibility - [Make an API AI-Ready](../../../cloud/develop-api-proxy/make-api-ai-ready.md): per-API agent visibility, and writing descriptions agents can use - [Manage Views](manage-views.md): why these settings are per-view diff --git a/en/docs/next/api-portal/admin-settings/manage-api-workflows.md b/en/docs/next/api-portal/admin-settings/manage-api-workflows.md index 979121a9e..884c83cc8 100644 --- a/en/docs/next/api-portal/admin-settings/manage-api-workflows.md +++ b/en/docs/next/api-portal/admin-settings/manage-api-workflows.md @@ -109,5 +109,5 @@ Click the trash icon to delete a workflow. Deleting removes it from the gallery - [API Workflows](../api-workflows.md): what consumers and agents see, and the endpoints they use - [LLM Instructions](llm-instructions.md): the portal-wide AI discoverability toggle and the `llms.txt` header -- [AI Agent Discovery](../discover-apis/ai-agent-discovery.md): every agent-facing endpoint the portal serves -- [Artifact types](../artifact-types.md): whether this deployment serves API workflows at all +- [AI Agent Discovery](../ai-agent-discovery.md): every agent-facing endpoint the portal serves +- [Artifact types](../setting-up/artifact-types.md): whether this deployment serves API workflows at all diff --git a/en/docs/next/api-portal/admin-settings/manage-mcp-servers.md b/en/docs/next/api-portal/admin-settings/manage-mcp-servers.md index 21801f71d..a99855a47 100644 --- a/en/docs/next/api-portal/admin-settings/manage-mcp-servers.md +++ b/en/docs/next/api-portal/admin-settings/manage-mcp-servers.md @@ -53,8 +53,8 @@ MCP servers share the same row-level actions as APIs: Once published, a server appears in the **MCP Servers** catalog with its tools, resources, and prompts listed, an MCP Playground for trying them, and a client configuration snippet. See [MCP Servers](../mcp-servers/overview.md). -Servers can also be published without the wizard, through the portal's [MCP Registry API](../mcp-servers/mcp-registry.md). +Servers can also be published without the wizard, through the portal's [MCP Registry API](../mcp-registry.md). ## Artifact types -If you only want to expose MCP servers (hiding every other API type entirely), or the reverse, see [Artifact types](../artifact-types.md). +If you only want to expose MCP servers (hiding every other API type entirely), or the reverse, see [Artifact types](../setting-up/artifact-types.md). diff --git a/en/docs/next/api-portal/admin-settings/organization-settings.md b/en/docs/next/api-portal/admin-settings/organization-settings.md index 326f425d2..92272015a 100644 --- a/en/docs/next/api-portal/admin-settings/organization-settings.md +++ b/en/docs/next/api-portal/admin-settings/organization-settings.md @@ -26,7 +26,7 @@ The **Organization** tab in the API Portal's Settings page manages the details o |---|---| | **Name** | The display name shown throughout the portal UI | | **Handle** | The URL-safe identifier used in every portal URL (`/api-portal//views/`). Read-only—it can't be changed after the organization is created | -| **Artifact types served** | Read-only. Shows which artifact types the portal serves—APIs, Model Context Protocol (MCP) servers, and API workflows. Set by the operator in the `[api_portal.artifacts]` config, not from this pane; pages for a type that isn't served return 404. See [Artifact types](../artifact-types.md) | +| **Artifact types served** | Read-only. Shows which artifact types the portal serves—APIs, Model Context Protocol (MCP) servers, and API workflows. Set by the operator in the `[api_portal.artifacts]` config, not from this pane; pages for a type that isn't served return 404. See [Artifact types](../setting-up/artifact-types.md) | | **Business owner** | Contact name for the organization owner | | **Business owner contact** | The owner's phone number or other contact string | | **Business owner email** | The owner's email address | diff --git a/en/docs/next/api-portal/admin-settings/theming.md b/en/docs/next/api-portal/admin-settings/theming.md index e87cb1dee..03ecbe3e8 100644 --- a/en/docs/next/api-portal/admin-settings/theming.md +++ b/en/docs/next/api-portal/admin-settings/theming.md @@ -1,6 +1,6 @@ --- -title: "Apply a theme to a view" -description: "Upload a theme ZIP to a view in the API Portal & MCP Hub, download the current theme, or reset to the built-in default." +title: "Theming the API Portal & MCP Hub" +description: "Build a custom theme for a view—override styles, layouts, partials, and pages, re-color the portal from a few seed variables, and package it for upload." canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/theming/ md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/theming.md tags: @@ -8,47 +8,190 @@ tags: - api-portal - theming author: WSO2 API Platform Documentation Team -last_updated: 2026-07-23 +last_updated: 2026-07-31 content_type: "how-to" --- -# Apply a theme +# Theming -The **Theming** tab in the API Portal's Settings page is where you upload a theme to a view, download what's currently applied, and roll back to the built-in default. +A theme changes how a [view](manage-views.md) looks and behaves: its colors, page shell, header and footer, and the markup of individual pages. You build one as a directory of files, package it as a ZIP, and an admin applies it to a view. -This page covers the panel. To build the theme in the first place—which files you can override, how the color tokens work, and how to package the ZIP—see [Theming](../theming.md). +Theming is scoped **per view**, so one organization can serve a branded partner portal and a plain internal one from the same catalog. -Theming is configured **per view**: if your organization has more than one [view](manage-views.md), use the view switcher at the top of the panel to pick which one you're theming. +## How a theme works -## Applying a custom theme +The portal renders every page from a complete default template tree at `src/defaultContent/`. A theme is a **partial copy of that tree**: include only the files you want to change, and every file you leave out is served from the default automatically. -1. Navigate to **Settings** and select the **Theming** tab under **APPEARANCE**. -2. Pick the view you want to theme, if you have more than one. -3. Under **Upload Theme**, drag and drop (or browse to) a single ZIP archive, up to 10 MB. +That has two consequences worth internalizing: - The archive must hold **one wrapper directory** containing the theme's folders—`my-theme/styles/`, `my-theme/layout/`, `my-theme/partials/`, `my-theme/pages/`, `my-theme/images/`—not those folders at the archive root. See [Package the theme](../theming.md#package-the-theme); getting this wrong makes the layout silently not apply. +- **A theme is not just colors.** Any file under `src/defaultContent` can be overridden—this includes the page shell, partials, and the markup of individual pages. +- **You never fork the whole tree.** A color-only theme is one file. Adding a custom API listing is two. -4. Click **Apply theme**. +### What you can override -!!! warning "Uploading replaces the current theme entirely" - Applying a new ZIP replaces this view's existing custom theme files completely—it isn't merged with what was there before. Use **Download theme** first if you want a backup or a starting point to edit. +These are the files the example theme replaces, and what each one governs: -The panel's **Current theme** indicator shows whether the view is using a custom uploaded theme or the built-in default. +| Path in the theme | Controls | +|---|---| +| `styles/main.css` | The portal-wide stylesheet, including the color tokens | +| `layout/main.hbs` | The outer HTML shell—``, page ``, nav frame | +| `partials/header.hbs` | The top bar | +| `partials/footer.hbs` | The footer | +| `pages/home/partials/home.hbs` | The home page body | +| `pages/apis/partials/api-listing.hbs` | How APIs are listed, including the card markup | +| `pages/api-landing/partials/api-detail-banner.hbs` | The header block on an API's overview page | +| `images/` | Logo and other image assets. Optional, and the example below ships none | -## Downloading the current theme +Any other `pages/**/partials/*.hbs` file from the default tree works the same way—those are just the ones the example below uses. -Click **Download theme** to get a ZIP of what's currently applied to the view. If the view has no custom theme, this downloads the built-in default theme instead—so it always gives you a valid starting point to customize. +!!! note + JavaScript can't be added as a theme asset, and templates are validated on upload. Portal behavior comes from the portal's own scripts; a theme covers CSS, Handlebars templates, and images. -## Resetting to the default theme +## Colors come from a few seeds -Click **Reset to default** to discard the view's custom theme files and revert to the built-in default. This can't be undone—download a copy first if you might want it again. +`styles/main.css` defines its palette as a small set of **seed** variables, with everything else **derived** from them through `color-mix()`. Change the seeds and the whole portal re-colors—text ramp, borders, surfaces, gradients, the dark hero and sidebar included. -## API-level content +The seeds are: -Theming here controls the view-wide look and feel. To change one API's overview body, set its icon, or attach documents, see [Customize an API's Content](api-content.md). +```css +:root { + /* SEEDS · brand — these cascade everywhere */ + --primary: #1a4c6d; /* structure, links, focus, primary buttons */ + --primary-dark: #043556; /* primary hover / pressed / deep panels */ + --primary-light: #2b719f; /* lifted primary states, on-dark highlights */ + --accent: #fe8c3a; /* solid highlights, badges, env dots, borders */ + --accent-dark: #ef4223; + --accent-light: #ff8636; + + /* SEEDS · neutral foundation */ + --ink: #1a2433; /* darkest text — generates the whole grey ramp */ + --surface: #ffffff; /* page background — the ramp mixes toward this */ + --white: #ffffff; /* fills on colored or dark surfaces */ + + /* SEEDS · semantic — deliberately independent of the brand */ + --success: #2e7d32; + --warning: #e39a00; + --danger: #c62828; + --info: #0277bd; +} +``` + +Everything below them in the file is derived, for example: + +```css +--text: color-mix(in srgb, var(--ink) 80%, var(--surface)); +--border: color-mix(in srgb, var(--ink) 12%, var(--surface)); +--focus-ring: color-mix(in srgb, var(--primary) 40%, transparent); +``` + +So a re-color means editing the seed block and nothing else. + +!!! important "Copy `main.css` whole" + Don't replace `main.css` with just a `:root` override. It carries both the variables *and* the rule definitions, plus the `@import` statements that pull in the other stylesheets (`home.css`, `header.css`, and the rest). Those imported files are served from the defaults and don't need copying—but the file that imports them does. + +## Example: a teal and coral theme + +Here's a complete theme that re-colors the portal and replaces the wordmark, home hero, and API listing. It's seven files: + +```text +my-theme/ +├── styles/main.css # seeds changed, plus a few custom rules +├── layout/main.hbs # page shell and <title> +├── partials/header.hbs # text wordmark instead of the logo image +├── partials/footer.hbs # custom footer links +└── pages/ + ├── home/partials/home.hbs # custom hero copy + ├── apis/partials/api-listing.hbs # hero, plus a different card type and arrangement + └── api-landing/partials/api-detail-banner.hbs # custom API overview header +``` + +Everything else—the other pages, the sidebar, the imported stylesheets—is served from the defaults untouched. + +### The color change + +`styles/main.css` is a copy of the default with its seed block swapped. That single edit is the whole re-color: + +```css +:root { + /* SEEDS · brand — teal and coral instead of the default navy and orange */ + --primary: #0f766e; + --primary-dark: #134e4a; + --primary-light: #2dd4bf; + --accent: #fb7185; + --accent-dark: #e11d48; + --accent-light: #fda4af; + + /* SEEDS · neutral foundation — --ink generates the whole grey ramp */ + --ink: #0e2a2a; + --surface: #ffffff; + --white: #ffffff; +} +``` + +The dark hero, the sidebar gradient, borders, and the text ramp all follow from those, because they're derived rather than hardcoded. + +### Replacing the wordmark + +`partials/header.hbs` swaps the logo image for text and an icon, which shows that branding doesn't require shipping a new asset: + +```handlebars +<a class="navbar-brand d-flex align-items-center brand-wordmark" href="{{baseUrl}}"> + <i class="bi bi-water brand-wordmark-icon" aria-hidden="true"></i> + <span class="brand-wordmark-text">Green</span> +</a> +``` + +The classes it introduces are styled by a small block appended to `main.css`, alongside the custom listing and overview treatments. + +### Starting from it + +A working copy of this theme sits at [`samples/layouts/green-theme/`](https://github.com/wso2/api-platform/tree/main/portals/api-portal/samples/layouts/green-theme) in the API Platform repository, and at the same path in the portal distribution. Copy it, change the seeds, and delete any override you don't want: + +```bash +cp -r samples/layouts/green-theme/ my-theme/ +``` + +## Build and preview + +[Design Mode](design-mode.md) is the fastest way to iterate: it serves the portal from a theme directory on disk with sample data, no database or identity provider needed, and picks up file edits on reload. + +Point it at your theme directory: + +```toml +[api_portal.design_mode] +enabled = true +path_to_layout = "./my-theme/" +``` + +To preview the example above before editing it, point `path_to_layout` at `./samples/layouts/green-theme/`. + +## Package the theme + +The ZIP must contain **one wrapper directory** holding the theme, not the theme's folders at the root. Zip the directory from its parent: + +```bash +zip -r my-theme.zip my-theme/ +``` + +That produces `my-theme/styles/main.css`, `my-theme/layout/main.hbs`, and so on. + +!!! warning "A wrapper directory is required" + The portal classifies each file by the path *below* the first segment. Zipping from inside the theme directory—so that `layout/` and `styles/` sit at the ZIP root—makes `layout/main.hbs` register as a generic template rather than the page shell, and your layout silently won't apply. + +Uploads are size-limited; see `uploads.max_bytes` in [Configurations](../references/configurations.md#uploads). + +## Apply it + +An admin uploads the ZIP to a view under **Settings** → **Theming**. Applying a theme replaces that view's existing theme files entirely rather than merging with them. See [Apply a Theme](theming.md) for the panel, and for downloading the current theme or resetting to the default. + +## Theming vs. API content + +A theme applies to every page in a view. To change one API's overview body—or set its icon, or attach documents—you upload content against that API instead, which leaves the rest of the view alone. See [Customize an API's Content](api-content.md). ## Related -- [Theming](../theming.md): build a theme, and the sample you can start from -- [Design Mode](../setting-up/design-mode.md): preview a theme before uploading it -- [Manage Views](manage-views.md): themes are scoped per view +- [Apply a Theme](theming.md): upload, download, and reset a view's theme +- [Design Mode](design-mode.md): preview a theme offline against sample data +- [Manage Views](manage-views.md): why themes are per-view +- [Customize an API's Content](api-content.md): change one API's overview body instead of the whole view +- [Configurations](../references/configurations.md#uploads): upload size limits diff --git a/en/docs/next/api-portal/admin-settings/webhook-integration.md b/en/docs/next/api-portal/admin-settings/webhook-integration.md index a7237746d..1a8d94aaa 100644 --- a/en/docs/next/api-portal/admin-settings/webhook-integration.md +++ b/en/docs/next/api-portal/admin-settings/webhook-integration.md @@ -72,4 +72,4 @@ You can read delivery history, including failures and their HTTP status, through - [Webhook Event Catalog](../references/webhook-event-catalog.md): every event's payload, headers, signing, and encryption - [Webhook Subscribers](../rest-api/webhook-subscribers.md): manage subscribers through the Management API - [Webhook Events](../rest-api/webhook-events.md): read delivery history through the Management API -- [Manage API Keys](../manage-api-keys.md): the lifecycle behind the `apikey.*` events +- [Manage API Keys](../consume-an-api/manage-api-keys.md): the lifecycle behind the `apikey.*` events diff --git a/en/docs/next/api-portal/discover-apis/ai-agent-discovery.md b/en/docs/next/api-portal/ai-agent-discovery.md similarity index 75% rename from en/docs/next/api-portal/discover-apis/ai-agent-discovery.md rename to en/docs/next/api-portal/ai-agent-discovery.md index 9b8457f52..d86a7173a 100644 --- a/en/docs/next/api-portal/discover-apis/ai-agent-discovery.md +++ b/en/docs/next/api-portal/ai-agent-discovery.md @@ -1,8 +1,8 @@ --- title: "AI agent API discovery" description: "Expose published APIs, MCP servers, and workflows through llms.txt and machine-readable Markdown endpoints so AI agents can discover and invoke them." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/ai-agent-discovery/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/ai-agent-discovery.md +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/ai-agent-discovery/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/ai-agent-discovery.md tags: - cloud - api-portal @@ -18,7 +18,7 @@ The API Portal & MCP Hub has built-in support for AI agent discoverability. Ever This page explains what those endpoints return and how agents navigate the portal. -Every endpoint below is scoped to an organization handle (`{orgName}`) and a [view](../admin-settings/manage-views.md) (`{viewName}`, which is `default` unless an admin has created more views). Responses are plain text or JSON, so agents can fetch them without authentication, JavaScript rendering, or a browser. Only discovery is unauthenticated—invoking an API or MCP server still requires its credentials, a subscription where one applies, and the scopes it declares. +Every endpoint below is scoped to an organization handle (`{orgName}`) and a [view](admin-settings/manage-views.md) (`{viewName}`, which is `default` unless an admin has created more views). Responses are plain text or JSON, so agents can fetch them without authentication, JavaScript rendering, or a browser. Only discovery is unauthenticated—invoking an API or MCP server still requires its credentials, a subscription where one applies, and the scopes it declares. ## `llms.txt`: The entry point for agents @@ -30,7 +30,7 @@ The portal generates an `llms.txt` file on every request—a Markdown index desi GET /api-portal/{orgName}/views/{viewName}/llms.txt ``` -The file opens with the portal's name and description, both configured through [LLM Instructions](../admin-settings/llm-instructions.md). It then lists every agent-visible artifact, grouped into the following sections, with each entry linking to that artifact's own Markdown document: +The file opens with the portal's name and description, both configured through [LLM Instructions](admin-settings/llm-instructions.md). It then lists every agent-visible artifact, grouped into the following sections, with each entry linking to that artifact's own Markdown document: | Section | Contains | |---|---| @@ -44,7 +44,7 @@ The file opens with the portal's name and description, both configured through [ A section is omitted when it holds no agent-visible artifacts. An agent that starts at `llms.txt` therefore learns the full scope of the catalog without crawling the portal, which makes it the standard starting point for LLM-native API consumption. !!! tip - Portal admins set the name and description that head `llms.txt` under [LLM Instructions](../admin-settings/llm-instructions.md). The same page carries the toggle that turns AI discoverability on or off for the whole portal. + Portal admins set the name and description that head `llms.txt` under [LLM Instructions](admin-settings/llm-instructions.md). The same page carries the toggle that turns AI discoverability on or off for the whole portal. ## Machine-readable endpoints @@ -112,21 +112,21 @@ A typical agent discovery flow looks like this: 5. **Fetch the specification separately.** When the agent needs the specification as a parseable file rather than as inlined text, it retrieves the `specification.*` endpoint for the API's type. 6. **Follow a workflow.** If a published workflow matches the task, the agent retrieves the Arazzo specification and agent prompt, then follows a vetted, step-by-step call sequence instead of reasoning from scratch. -The **Try with AI** button on an [API's overview page](browse-apis.md#open-an-api) hands this flow to an agent directly. It produces a prompt that points the agent at that API's `.md` URL and asks it to summarize the API before doing anything else. +The **Try with AI** button on an [API's overview page](discover-apis/browse-apis.md#open-an-api) hands this flow to an agent directly. It produces a prompt that points the agent at that API's `.md` URL and asks it to summarize the API before doing anything else. ## Visibility controls Three separate controls decide what agents see: -- **Per-artifact agent visibility.** All published APIs, MCP servers, and workflows are agent-visible by default. Setting an artifact to hidden removes it from `llms.txt`, the catalogs, and every Markdown and specification endpoint, while leaving it visible to human users in the portal. For APIs, see [Make an API AI-Ready](../../../cloud/develop-api-proxy/make-api-ai-ready.md). For workflows, see [Managing API Workflows](../admin-settings/manage-api-workflows.md). -- **Portal-wide AI discoverability.** Turning off **Portal is AI-discoverable** under [LLM Instructions](../admin-settings/llm-instructions.md) makes every endpoint on this page return `404`, including `llms.txt` itself. -- **Served artifact types.** A deployment that doesn't serve a given artifact type returns `404` for that type's catalog, Markdown, and specification endpoints. See [Artifact types](../artifact-types.md). +- **Per-artifact agent visibility.** All published APIs, MCP servers, and workflows are agent-visible by default. Setting an artifact to hidden removes it from `llms.txt`, the catalogs, and every Markdown and specification endpoint, while leaving it visible to human users in the portal. For APIs, see [Make an API AI-Ready](../../cloud/develop-api-proxy/make-api-ai-ready.md). For workflows, see [Managing API Workflows](admin-settings/manage-api-workflows.md). +- **Portal-wide AI discoverability.** Turning off **Portal is AI-discoverable** under [LLM Instructions](admin-settings/llm-instructions.md) makes every endpoint on this page return `404`, including `llms.txt` itself. +- **Served artifact types.** A deployment that doesn't serve a given artifact type returns `404` for that type's catalog, Markdown, and specification endpoints. See [Artifact types](setting-up/artifact-types.md). ## Related -- [LLM Instructions](../admin-settings/llm-instructions.md): set the portal name and description at the top of `llms.txt`, and toggle AI discoverability -- [Managing API Workflows](../admin-settings/manage-api-workflows.md): publish workflows that guide agents through common multi-step use cases -- [API Workflows](../api-workflows.md): how agents discover and follow published workflows -- [Make an API AI-Ready](../../../cloud/develop-api-proxy/make-api-ai-ready.md): publisher guidance on descriptions, specifications, and visibility settings -- [MCP Servers](../mcp-servers/overview.md): how MCP servers are published, discovered, and connected to -- [MCP Registry API](../mcp-servers/mcp-registry.md): discover the same servers in the Model Context Protocol registry format +- [LLM Instructions](admin-settings/llm-instructions.md): set the portal name and description at the top of `llms.txt`, and toggle AI discoverability +- [Managing API Workflows](admin-settings/manage-api-workflows.md): publish workflows that guide agents through common multi-step use cases +- [API Workflows](api-workflows.md): how agents discover and follow published workflows +- [Make an API AI-Ready](../../cloud/develop-api-proxy/make-api-ai-ready.md): publisher guidance on descriptions, specifications, and visibility settings +- [MCP Servers](mcp-servers/overview.md): how MCP servers are published, discovered, and connected to +- [MCP Registry API](mcp-registry.md): discover the same servers in the Model Context Protocol registry format diff --git a/en/docs/next/api-portal/api-workflows.md b/en/docs/next/api-portal/api-workflows.md index dd1cac1c2..f3ed7ca02 100644 --- a/en/docs/next/api-portal/api-workflows.md +++ b/en/docs/next/api-portal/api-workflows.md @@ -87,7 +87,7 @@ A typical agent flow is: read `llms.txt` or `api-workflows.md` to see what exist When a workflow's source URL points at an API published in this same portal, the portal rewrites that link to the API's Markdown documentation, so an agent stays inside the machine-readable surface instead of being sent to a rendered page. -For the portal's full set of agent-facing endpoints, see [AI Agent Discovery](discover-apis/ai-agent-discovery.md). +For the portal's full set of agent-facing endpoints, see [AI Agent Discovery](ai-agent-discovery.md). ## Workflow visibility @@ -105,6 +105,6 @@ Turning off **Portal is AI-discoverable** under [LLM Instructions](admin-setting ## Related - [Managing API Workflows](admin-settings/manage-api-workflows.md): author, publish, and set the visibility of workflows -- [AI Agent Discovery](discover-apis/ai-agent-discovery.md): the portal's full set of agent-facing endpoints +- [AI Agent Discovery](ai-agent-discovery.md): the portal's full set of agent-facing endpoints - [Consume an API](consume-an-api/overview.md): the credentials you'll need for the APIs a workflow calls -- [Artifact types](artifact-types.md): whether this deployment serves API workflows at all +- [Artifact types](setting-up/artifact-types.md): whether this deployment serves API workflows at all diff --git a/en/docs/next/api-portal/concepts.md b/en/docs/next/api-portal/concepts.md index 667df4047..858e61537 100644 --- a/en/docs/next/api-portal/concepts.md +++ b/en/docs/next/api-portal/concepts.md @@ -48,11 +48,11 @@ A **label** is a tag you assign to an API or MCP server to control which views e ## Layout -A **layout** is a custom Handlebars template that defines the structure of a view's pages. It's one part of a **theme**—a partial copy of the portal's default template tree, holding only the styles, layouts, partials, and page templates you want to change. Anything a theme omits is served from the default. See [Theming](theming.md), and [Design Mode](setting-up/design-mode.md) for building one offline. +A **layout** is a custom Handlebars template that defines the structure of a view's pages. It's one part of a **theme**—a partial copy of the portal's default template tree, holding only the styles, layouts, partials, and page templates you want to change. Anything a theme omits is served from the default. See [Theming](admin-settings/theming.md), and [Design Mode](admin-settings/design-mode.md) for building one offline. ## Artifact types -The portal serves three kinds of artifact: **APIs**, **MCP servers**, and **API workflows**. Which of them a given portal exposes is an operator setting—the `enabled_types` allowlist in the `[api_portal.artifacts]` config. A type that isn't enabled gets no navigation entry and no landing-page section, and its routes return `404`. See [Artifact types](artifact-types.md). +The portal serves three kinds of artifact: **APIs**, **MCP servers**, and **API workflows**. Which of them a given portal exposes is an operator setting—the `enabled_types` allowlist in the `[api_portal.artifacts]` config. A type that isn't enabled gets no navigation entry and no landing-page section, and its routes return `404`. See [Artifact types](setting-up/artifact-types.md). ## API @@ -86,7 +86,7 @@ Plans can define rate limits—a request count (or event count, for async APIs) An **application** is a logical container—representing a mobile app, web app, device, or script—that a developer creates in the portal. For OAuth2-secured APIs, an application holds the client ID(s) that link to OAuth applications created directly in a key manager; the portal never generates or stores consumer key/secret pairs. -A developer can have multiple applications, each with independent OAuth2 client IDs. See [Manage Applications](manage-applications.md). +A developer can have multiple applications, each with independent OAuth2 client IDs. See [Manage Applications](consume-an-api/manage-applications.md). !!! note Applications are not required for subscriptions or API key generation. Subscriptions are made directly to an API or MCP server, and API keys are bound to an API or MCP server—not to an application. @@ -95,13 +95,13 @@ A developer can have multiple applications, each with independent OAuth2 client A **subscription** is a developer's access grant to a specific API or MCP server under a chosen subscription plan. The plan determines the developer's rate limits and quota for that artifact. -Subscriptions are made directly to the artifact—no application is involved. Once subscribed, the developer can invoke it under the terms of the chosen plan. See [Manage Subscriptions](manage-subscriptions.md). +Subscriptions are made directly to the artifact—no application is involved. Once subscribed, the developer can invoke it under the terms of the chosen plan. See [Manage Subscriptions](consume-an-api/manage-subscriptions.md). ## API key An **API key** is a simple token bound to a specific API or MCP server, used to authenticate requests to artifacts that use API key-based authentication. API keys are generated per artifact—not per application or per subscription. -API keys can be generated, regenerated (rotated), or revoked. Each of those publishes a webhook event, so a handler in front of your API Gateway can enforce the change once it receives the event—see the [Webhook Event Catalog](references/webhook-event-catalog.md). See [Manage API Keys](manage-api-keys.md). +API keys can be generated, regenerated (rotated), or revoked. Each of those publishes a webhook event, so a handler in front of your API Gateway can enforce the change once it receives the event—see the [Webhook Event Catalog](references/webhook-event-catalog.md). See [Manage API Keys](consume-an-api/manage-api-keys.md). !!! note The portal generates keys through the UI for REST, WebSocket, and WebSub APIs whose definition declares API-key security. Keys for GraphQL and SOAP APIs exist solely through the [API Keys](rest-api/api-keys.md) Management API. Keys for MCP servers exist only through the [MCP Server Keys](rest-api/mcp-server-keys.md) Management API. diff --git a/en/docs/next/api-portal/consume-an-api/api-key.md b/en/docs/next/api-portal/consume-an-api/api-key.md index 3e91071f7..15a709bb4 100644 --- a/en/docs/next/api-portal/consume-an-api/api-key.md +++ b/en/docs/next/api-portal/consume-an-api/api-key.md @@ -19,12 +19,12 @@ An API key is bound to one API or Model Context Protocol (MCP) server. You gener ## Prerequisites - The API declares API key security. Look for a `securitySchemes` entry of type `apiKey` in the API's specification. -- You have a key. See [Manage API Keys](../manage-api-keys.md) for the generate, rotate, and revoke lifecycle. +- You have a key. See [Manage API Keys](manage-api-keys.md) for the generate, rotate, and revoke lifecycle. !!! note The portal generates keys for REST, WebSocket, and WebSub APIs whose specification declares API key security. GraphQL, SOAP, and MCP artifacts get no **API Keys** button even when their specification declares an `apiKey` scheme—for those, obtain the key from whoever operates the API and send it the same way. -- If the API has subscription plans, [subscribe to one](../manage-subscriptions.md). The subscription is a separate credential from the key, and you send both. +- If the API has subscription plans, [subscribe to one](manage-subscriptions.md). The subscription is a separate credential from the key, and you send both. ## Invoke the API @@ -59,6 +59,6 @@ Open the API's [documentation page](../discover-apis/browse-apis.md#read-the-spe ## Related - [Consume an API](overview.md): which credentials an API expects, and how to tell -- [Manage API Keys](../manage-api-keys.md): generate, rotate, revoke, and associate keys with an application -- [Manage Subscriptions](../manage-subscriptions.md): get a subscription token +- [Manage API Keys](manage-api-keys.md): generate, rotate, revoke, and associate keys with an application +- [Manage Subscriptions](manage-subscriptions.md): get a subscription token - [Consume an API Secured with OAuth2](oauth2.md): the alternative for OAuth2-secured APIs diff --git a/en/docs/next/api-portal/manage-api-keys.md b/en/docs/next/api-portal/consume-an-api/manage-api-keys.md similarity index 88% rename from en/docs/next/api-portal/manage-api-keys.md rename to en/docs/next/api-portal/consume-an-api/manage-api-keys.md index 39967549f..c173ade6e 100644 --- a/en/docs/next/api-portal/manage-api-keys.md +++ b/en/docs/next/api-portal/consume-an-api/manage-api-keys.md @@ -1,8 +1,8 @@ --- title: "Manage API keys" description: "Generate, rotate, revoke, and associate API keys for a subscribed API in the API Portal & MCP Hub." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/manage-api-keys/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/manage-api-keys.md +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/manage-api-keys/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/manage-api-keys.md tags: - cloud - api-portal @@ -31,7 +31,7 @@ The API must have API key authentication enabled—check the API's documentation 7. **Copy the API key immediately.** The key won't be visible in the UI after you close this dialog. 8. Click **Done**. -Once you have a key, see [Consume an API Secured with an API Key](consume-an-api/api-key.md) for how to use it. +Once you have a key, see [Consume an API Secured with an API Key](api-key.md) for how to use it. ## Rotate an API key @@ -87,12 +87,12 @@ When you generate, regenerate, or revoke an API key, the portal publishes a webh Changing a key's application association publishes `apikey.application_updated`. That's an analytics notification—it doesn't affect whether the key works. -The key itself travels encrypted on `apikey.generated` and `apikey.regenerated`. For the payload of each event, see the [Webhook Event Catalog](references/webhook-event-catalog.md); to register a subscriber, see [Webhook Integration](admin-settings/webhook-integration.md). +The key itself travels encrypted on `apikey.generated` and `apikey.regenerated`. For the payload of each event, see the [Webhook Event Catalog](../references/webhook-event-catalog.md); to register a subscriber, see [Webhook Integration](../admin-settings/webhook-integration.md). ## Related - [Manage Subscriptions](manage-subscriptions.md)—subscribe if the API requires a subscription -- [Consume an API Secured with an API Key](consume-an-api/api-key.md)—use the key to invoke the API -- [Consume an API Secured with OAuth2](consume-an-api/oauth2.md)—alternative for OAuth2-secured APIs +- [Consume an API Secured with an API Key](api-key.md)—use the key to invoke the API +- [Consume an API Secured with OAuth2](oauth2.md)—alternative for OAuth2-secured APIs - [Manage Applications](manage-applications.md)—set up an application to associate keys with -- [Webhook Event Catalog](references/webhook-event-catalog.md)—the `apikey.*` events this lifecycle publishes +- [Webhook Event Catalog](../references/webhook-event-catalog.md)—the `apikey.*` events this lifecycle publishes diff --git a/en/docs/next/api-portal/manage-applications.md b/en/docs/next/api-portal/consume-an-api/manage-applications.md similarity index 89% rename from en/docs/next/api-portal/manage-applications.md rename to en/docs/next/api-portal/consume-an-api/manage-applications.md index 0a0b09339..bdc5caa51 100644 --- a/en/docs/next/api-portal/manage-applications.md +++ b/en/docs/next/api-portal/consume-an-api/manage-applications.md @@ -1,8 +1,8 @@ --- title: "Manage applications in the API Portal & MCP Hub" description: "Create an application to hold OAuth2 client IDs, edit its name and description, associate API keys for analytics, and delete it." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/manage-applications/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/manage-applications.md +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/manage-applications/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/manage-applications.md tags: - cloud - api-portal @@ -52,7 +52,7 @@ Both fields edit in place on the detail page: ## Link OAuth2 credentials -The **Manage Keys** section is where you paste a client ID from your key manager and generate access tokens against it. That's the whole reason applications exist—see [Consume an API Secured with OAuth2](consume-an-api/oauth2.md) for the full sequence. +The **Manage Keys** section is where you paste a client ID from your key manager and generate access tokens against it. That's the whole reason applications exist—see [Consume an API Secured with OAuth2](oauth2.md) for the full sequence. ## Associate an API key @@ -80,8 +80,8 @@ Deleting is irreversible, and it's narrower than the confirmation dialog suggest ## Related -- [Consume an API Secured with OAuth2](consume-an-api/oauth2.md): link a client ID and generate an access token +- [Consume an API Secured with OAuth2](oauth2.md): link a client ID and generate an access token - [Manage Subscriptions](manage-subscriptions.md): subscriptions are made directly to an API, not through an application - [Manage API Keys](manage-api-keys.md): generate a key and optionally associate it with an application -- [Key Manager Integration](admin-settings/key-manager-integration.md): admin guide for connecting key managers -- [Webhook Event Catalog](references/webhook-event-catalog.md): the `application.*` events, and what deletion does to associated keys +- [Key Manager Integration](../admin-settings/key-manager-integration.md): admin guide for connecting key managers +- [Webhook Event Catalog](../references/webhook-event-catalog.md): the `application.*` events, and what deletion does to associated keys diff --git a/en/docs/next/api-portal/manage-subscriptions.md b/en/docs/next/api-portal/consume-an-api/manage-subscriptions.md similarity index 82% rename from en/docs/next/api-portal/manage-subscriptions.md rename to en/docs/next/api-portal/consume-an-api/manage-subscriptions.md index 4d8912ad7..2c0dd2897 100644 --- a/en/docs/next/api-portal/manage-subscriptions.md +++ b/en/docs/next/api-portal/consume-an-api/manage-subscriptions.md @@ -1,8 +1,8 @@ --- title: "Manage subscriptions in the API Portal & MCP Hub" description: "Subscribe to an API under a plan, use the subscription token it issues, and switch, suspend, or cancel the subscription later." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/manage-subscriptions/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/manage-subscriptions.md +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/manage-subscriptions/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/manage-subscriptions.md tags: - cloud - api-portal @@ -41,7 +41,7 @@ curl -X GET "https://api.example.com/orders/v1/orders" \ -H "Subscription-Key: <YOUR_SUBSCRIPTION_TOKEN>" ``` -The subscription token is not a substitute for authentication. Most APIs also expect an API key or an OAuth2 access token, and you send both headers together. See [Consume an API](consume-an-api/overview.md) for which credentials an API expects and how to tell. +The subscription token is not a substitute for authentication. Most APIs also expect an API key or an OAuth2 access token, and you send both headers together. See [Consume an API](overview.md) for which credentials an API expects and how to tell. ## View and manage a subscription @@ -76,16 +76,16 @@ Cancelling invalidates the subscription token. Anything calling the API with it Not every API has subscription plans. When an API doesn't, its overview page shows no **Subscription plans** panel and no **Subscribe** button, and there's nothing to subscribe to—call it with whatever authentication its specification requires. -To try an API before wiring it into your code, use the **Try It** console on the [documentation page](discover-apis/browse-apis.md#read-the-specification-and-try-it) rather than looking for a subscription. +To try an API before wiring it into your code, use the **Try It** console on the [documentation page](../discover-apis/browse-apis.md#read-the-specification-and-try-it) rather than looking for a subscription. ## Subscription plans -Plans set how much of an API you can consume. The API publisher decides which plans an API offers, and each plan's rate limit appears on its card. For how admins define them, see [Subscription Plans](admin-settings/subscription-plans.md). +Plans set how much of an API you can consume. The API publisher decides which plans an API offers, and each plan's rate limit appears on its card. For how admins define them, see [Subscription Plans](../admin-settings/subscription-plans.md). ## Related -- [Consume an API](consume-an-api/overview.md): which credentials an API expects, and how they combine -- [API Overview](discover-apis/browse-apis.md#open-an-api): where the subscription plans panel lives +- [Consume an API](overview.md): which credentials an API expects, and how they combine +- [API Overview](../discover-apis/browse-apis.md#open-an-api): where the subscription plans panel lives - [Manage API Keys](manage-api-keys.md): generate an API key for a subscribed API -- [Subscription Plans](admin-settings/subscription-plans.md): admin guide for defining plans -- [Webhook Event Catalog](references/webhook-event-catalog.md): the `subscription.*` events subscribing, switching, and unsubscribing publish +- [Subscription Plans](../admin-settings/subscription-plans.md): admin guide for defining plans +- [Webhook Event Catalog](../references/webhook-event-catalog.md): the `subscription.*` events subscribing, switching, and unsubscribing publish diff --git a/en/docs/next/api-portal/consume-an-api/oauth2.md b/en/docs/next/api-portal/consume-an-api/oauth2.md index 594e06793..8baac653e 100644 --- a/en/docs/next/api-portal/consume-an-api/oauth2.md +++ b/en/docs/next/api-portal/consume-an-api/oauth2.md @@ -21,8 +21,8 @@ OAuth2-secured APIs expect a bearer token issued by a key manager. The OAuth app Three things have to exist before you can generate a token: 1. **An OAuth application in a key manager.** Ask your admin which key managers your organization has connected and how to create an OAuth application in the one you need. For the admin side, see [Key Manager Integration](../admin-settings/key-manager-integration.md). -2. **An application in the portal.** This is the container that holds the client ID. See [Manage Applications](../manage-applications.md). -3. **A subscription, if the API has plans.** Subscriptions are made directly to the API, independently of your application. See [Manage Subscriptions](../manage-subscriptions.md). +2. **An application in the portal.** This is the container that holds the client ID. See [Manage Applications](manage-applications.md). +3. **A subscription, if the API has plans.** Subscriptions are made directly to the API, independently of your application. See [Manage Subscriptions](manage-subscriptions.md). ## Link a client ID @@ -106,7 +106,7 @@ Two things it doesn't do: it doesn't deregister or delete the OAuth application ## Related - [Consume an API](overview.md): which credentials an API expects, and how to tell -- [Manage Applications](../manage-applications.md): the container for client IDs -- [Manage Subscriptions](../manage-subscriptions.md): subscribe before generating credentials +- [Manage Applications](manage-applications.md): the container for client IDs +- [Manage Subscriptions](manage-subscriptions.md): subscribe before generating credentials - [Consume an API Secured with an API Key](api-key.md): the alternative for API-key-secured APIs - [Key Manager Integration](../admin-settings/key-manager-integration.md): admin guide for key manager setup diff --git a/en/docs/next/api-portal/consume-an-api/overview.md b/en/docs/next/api-portal/consume-an-api/overview.md index 8db0a092a..c7311bdd8 100644 --- a/en/docs/next/api-portal/consume-an-api/overview.md +++ b/en/docs/next/api-portal/consume-an-api/overview.md @@ -44,17 +44,17 @@ The per-API Markdown document at `/api-portal/{orgName}/views/{viewName}/api/{ap ### Subscription token -[Subscribe to the API](../manage-subscriptions.md) under a plan. The portal shows the token once, in a dialog, right after the subscription is created. +[Subscribe to the API](manage-subscriptions.md) under a plan. The portal shows the token once, in a dialog, right after the subscription is created. You can come back to it later: on the API's overview page, click **View subscription** on the plan you hold, then reveal or copy the token. The same dialog lets you regenerate the token—which invalidates the old one immediately—suspend the subscription, or unsubscribe. ### API key -Generate one from the **API Keys** page for that API, reachable from the **API Keys** button on the overview page or the sidebar submenu. See [Manage API Keys](../manage-api-keys.md) for the generate, rotate, and revoke lifecycle, and [Consume an API Secured with an API Key](api-key.md) for using it in a request. +Generate one from the **API Keys** page for that API, reachable from the **API Keys** button on the overview page or the sidebar submenu. See [Manage API Keys](manage-api-keys.md) for the generate, rotate, and revoke lifecycle, and [Consume an API Secured with an API Key](api-key.md) for using it in a request. ### OAuth2 access token -This one needs the most setup, because the OAuth application lives in a key manager rather than in the portal. In outline: create the OAuth application in your key manager, [create an application](../manage-applications.md) in the portal, link the client ID to it, then generate a token. See [Consume an API Secured with OAuth2](oauth2.md) for the full sequence. +This one needs the most setup, because the OAuth application lives in a key manager rather than in the portal. In outline: create the OAuth application in your key manager, [create an application](manage-applications.md) in the portal, link the client ID to it, then generate a token. See [Consume an API Secured with OAuth2](oauth2.md) for the full sequence. ## Try before you wire it up @@ -64,6 +64,6 @@ For REST APIs, the **Try It** console on the [documentation page](../discover-ap - [Consume an API Secured with OAuth2](oauth2.md) - [Consume an API Secured with an API Key](api-key.md) -- [Manage Subscriptions](../manage-subscriptions.md): where the subscription token comes from -- [Manage API Keys](../manage-api-keys.md): generate, rotate, and revoke keys -- [Manage Applications](../manage-applications.md): the container for OAuth2 client IDs +- [Manage Subscriptions](manage-subscriptions.md): where the subscription token comes from +- [Manage API Keys](manage-api-keys.md): generate, rotate, and revoke keys +- [Manage Applications](manage-applications.md): the container for OAuth2 client IDs diff --git a/en/docs/next/api-portal/discover-apis/browse-apis.md b/en/docs/next/api-portal/discover-apis/browse-apis.md index 5452734c3..3d94052fd 100644 --- a/en/docs/next/api-portal/discover-apis/browse-apis.md +++ b/en/docs/next/api-portal/discover-apis/browse-apis.md @@ -76,9 +76,9 @@ What the page shows depends on the API type. **Resources** lists every operation Two header buttons appear conditionally rather than always: - **API Keys** — only for REST, WebSocket, and WebSub APIs whose specification declares API key security. Never for GraphQL, SOAP, or MCP artifacts. -- **Try with AI** — only when the API is agent-visible. It opens a ready-made prompt that briefs an agent on the API using its [machine-readable documentation](ai-agent-discovery.md); copy it, download it as a `.txt` file, or send it straight to an assistant with **Run in Claude**. +- **Try with AI** — only when the API is agent-visible. It opens a ready-made prompt that briefs an agent on the API using its [machine-readable documentation](../ai-agent-discovery.md); copy it, download it as a `.txt` file, or send it straight to an assistant with **Run in Claude**. -Each plan in the **Subscription plans** panel carries its own **Subscribe** or **View subscription** button — see [Manage Subscriptions](../manage-subscriptions.md) for the full flow. +Each plan in the **Subscription plans** panel carries its own **Subscribe** or **View subscription** button — see [Manage Subscriptions](../consume-an-api/manage-subscriptions.md) for the full flow. ## Read the specification and try it @@ -104,6 +104,6 @@ Attached guides render in the same pane and cover what a specification can't: au - [APIs](overview.md): what an API is in the portal, and the types it publishes - [Which Credentials You Need](../consume-an-api/overview.md): work out what an API expects before you call it -- [Manage Subscriptions](../manage-subscriptions.md): subscribe, switch plans, and manage your subscription token +- [Manage Subscriptions](../consume-an-api/manage-subscriptions.md): subscribe, switch plans, and manage your subscription token - [Customize an API's Content](../admin-settings/api-content.md): replace the generated page body with your own - [Manage Views](../admin-settings/manage-views.md): how admins decide which APIs a view lists diff --git a/en/docs/next/api-portal/discover-apis/overview.md b/en/docs/next/api-portal/discover-apis/overview.md index 6ef270e51..808ce451a 100644 --- a/en/docs/next/api-portal/discover-apis/overview.md +++ b/en/docs/next/api-portal/discover-apis/overview.md @@ -46,10 +46,10 @@ An MCP server is another artifact in the same catalog, so most of what you know | Subscribed | Through an application that holds the credentials | Directly, without an application | | Agent visibility | The publisher sets it per API | Always agent-visible; the catalog marks every server **AI Ready** | -Everything else works the same way. APIs carry tags, labels, icons, subscription plans, and attached documents, and appear in the portal's [machine-readable endpoints](ai-agent-discovery.md) when the publisher marks them agent-visible. +Everything else works the same way. APIs carry tags, labels, icons, subscription plans, and attached documents, and appear in the portal's [machine-readable endpoints](../ai-agent-discovery.md) when the publisher marks them agent-visible. !!! note - A portal only serves APIs when its operator lists `apis` in `enabled_types`. Leave it out and the sidebar entry, the catalog, and every API route disappear. See [Artifact types](../artifact-types.md). + A portal only serves APIs when its operator lists `apis` in `enabled_types`. Leave it out and the sidebar entry, the catalog, and every API route disappear. See [Artifact types](../setting-up/artifact-types.md). ## How APIs reach the catalog @@ -72,6 +72,6 @@ The same artifact can be created programmatically, which is the route automation - [Manage APIs](../admin-settings/manage-apis.md): the admin side of adding and publishing APIs - [MCP Servers](../mcp-servers/overview.md): the other half of the catalog -- [AI Agent Discovery](ai-agent-discovery.md): the machine-readable endpoints that expose APIs to agents -- [Artifact types](../artifact-types.md): whether this deployment serves APIs +- [AI Agent Discovery](../ai-agent-discovery.md): the machine-readable endpoints that expose APIs to agents +- [Artifact types](../setting-up/artifact-types.md): whether this deployment serves APIs - [Concepts](../concepts.md): how APIs fit alongside MCP servers, plans, subscriptions, and keys diff --git a/en/docs/next/api-portal/getting-started.md b/en/docs/next/api-portal/getting-started.md index a95cd69e7..b22220ce9 100644 --- a/en/docs/next/api-portal/getting-started.md +++ b/en/docs/next/api-portal/getting-started.md @@ -246,8 +246,8 @@ Refresh the portal—the **Books API** now appears in the catalog. Select the ** - [Browse APIs](discover-apis/browse-apis.md): browse and search the catalog - [MCP Servers](mcp-servers/overview.md): publish and connect to Model Context Protocol servers -- [AI Agent Discovery](discover-apis/ai-agent-discovery.md): the `llms.txt` and Markdown endpoints agents use -- [Manage Applications](manage-applications.md): set up a container for OAuth2 credentials -- [Manage Subscriptions](manage-subscriptions.md): subscribe to a published API under a plan +- [AI Agent Discovery](ai-agent-discovery.md): the `llms.txt` and Markdown endpoints agents use +- [Manage Applications](consume-an-api/manage-applications.md): set up a container for OAuth2 credentials +- [Manage Subscriptions](consume-an-api/manage-subscriptions.md): subscribe to a published API under a plan - [Consume an API Secured with an API Key](consume-an-api/api-key.md) or [Consume an API Secured with OAuth2](consume-an-api/oauth2.md) -- [Theming](theming.md): customize the portal's look and feel +- [Theming](admin-settings/theming.md): customize the portal's look and feel diff --git a/en/docs/next/api-portal/mcp-servers/mcp-registry.md b/en/docs/next/api-portal/mcp-registry.md similarity index 94% rename from en/docs/next/api-portal/mcp-servers/mcp-registry.md rename to en/docs/next/api-portal/mcp-registry.md index 823fdb7a0..50b05fe2c 100644 --- a/en/docs/next/api-portal/mcp-servers/mcp-registry.md +++ b/en/docs/next/api-portal/mcp-registry.md @@ -1,8 +1,8 @@ --- title: "MCP Registry API" description: "Discover and publish MCP servers through the API Portal & MCP Hub's implementation of the Model Context Protocol registry specification." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/mcp-registry/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/mcp-registry.md +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-registry/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-registry.md tags: - cloud - api-portal @@ -204,19 +204,19 @@ Both take `{"status": "active" | "deprecated" | "deleted"}`. The first changes o The portal exposes MCP servers through two different APIs, and they aren't interchangeable: -| | MCP Registry API | [Management API](../rest-api/mcp-servers.md) | +| | MCP Registry API | [Management API](rest-api/mcp-servers.md) | |---|---|---| | Path | `/api-portal/registry/{orgHandle}/v0.1` | `/api-portal/api/v0.9` | | Shape | Model Context Protocol registry specification | The portal's own resource model | | Discovery auth | None | Bearer token | | Identified by | Reverse-DNS name plus version | Portal handle | -| Also manages | Nothing else | Content, documents, and [keys](../rest-api/mcp-server-keys.md) | +| Also manages | Nothing else | Content, documents, and [keys](rest-api/mcp-server-keys.md) | Use the registry when you want MCP-standard tooling to interoperate with the hub. Use the Management API when you're automating the portal itself. ## Related - [MCP Servers](overview.md): how registry-published servers behave in the catalog -- [Browse MCP Servers](browse-mcp-servers.md): the same servers, in the portal UI -- [MCP Servers (Management API)](../rest-api/mcp-servers.md): the portal's own CRUD API -- [Authentication](../rest-api/authentication.md): how to get a bearer token for the write endpoints +- [Browse MCP Servers](mcp-servers/browse-mcp-servers.md): the same servers, in the portal UI +- [MCP Servers (Management API)](rest-api/mcp-servers.md): the portal's own CRUD API +- [Authentication](rest-api/authentication.md): how to get a bearer token for the write endpoints diff --git a/en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md b/en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md index 0bdc69cb8..9b657cb0e 100644 --- a/en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md +++ b/en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md @@ -79,13 +79,13 @@ Every MCP server in the catalog is exposed through the portal's machine-readable | `/api-portal/{orgName}/views/{viewName}/mcp/{apiHandle}.md` | One server in Markdown: metadata, plans, attached documents, and its full tool, resource, and prompt list | | `/api-portal/{orgName}/views/{viewName}/mcp/{apiHandle}/docs/specification.json` | The raw tool, resource, and prompt schema | -Servers also appear in the portal's `llms.txt` index, under an **MCPs** section. See [AI Agent Discovery](../discover-apis/ai-agent-discovery.md). +Servers also appear in the portal's `llms.txt` index, under an **MCPs** section. See [AI Agent Discovery](../ai-agent-discovery.md). -For programmatic discovery in the Model Context Protocol registry format, use the [MCP Registry API](mcp-registry.md) instead. +For programmatic discovery in the Model Context Protocol registry format, use the [MCP Registry API](../mcp-registry.md) instead. ## Related - [Connect to an MCP Server](connect-to-an-mcp-server.md): credentials and client configuration - [MCP Servers](overview.md): what an MCP server is and how it reaches the catalog - [Browse APIs](../discover-apis/browse-apis.md): how catalog search matches terms -- [AI Agent Discovery](../discover-apis/ai-agent-discovery.md): the full set of agent-facing endpoints +- [AI Agent Discovery](../ai-agent-discovery.md): the full set of agent-facing endpoints diff --git a/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md b/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md index d858c0c6c..ee77f90ae 100644 --- a/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md +++ b/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md @@ -27,7 +27,7 @@ If the server has subscription plans, subscribe before you connect: The subscription is created immediately and a dialog shows your **subscription token**. Copy it before closing—send it as the `Subscription-Key` header when calling the server. -Subscriptions to MCP servers work exactly as they do for APIs: one per server, switchable between plans without changing the token, suspendable, and manageable from **Subscriptions** in the sidebar. See [Manage Subscriptions](../manage-subscriptions.md). +Subscriptions to MCP servers work exactly as they do for APIs: one per server, switchable between plans without changing the token, suspendable, and manageable from **Subscriptions** in the sidebar. See [Manage Subscriptions](../consume-an-api/manage-subscriptions.md). Servers with no plans need no subscription. Connect with whatever authentication the server itself expects. @@ -35,7 +35,7 @@ Servers with no plans need no subscription. Connect with whatever authentication The configuration snippet expects an OAuth2 access token in an `Authorization: Bearer` header. Get one the same way you would for an OAuth2-secured API: -1. [Create an application](../manage-applications.md) in the portal. +1. [Create an application](../consume-an-api/manage-applications.md) in the portal. 2. Link a client ID from your key manager to it under **Manage Keys**. 3. Generate an access token from the **Generate Token** tab, or with the ready-made command on the **cURL** tab. @@ -86,6 +86,6 @@ Before wiring the server into an agent, confirm the credentials work: open the s ## Related - [Browse MCP Servers](browse-mcp-servers.md): find a server and read its tools -- [Manage Subscriptions](../manage-subscriptions.md): plans, tokens, switching, and unsubscribing +- [Manage Subscriptions](../consume-an-api/manage-subscriptions.md): plans, tokens, switching, and unsubscribing - [Consume an API Secured with OAuth2](../consume-an-api/oauth2.md): the full token-generation sequence -- [Manage Applications](../manage-applications.md): where client IDs live +- [Manage Applications](../consume-an-api/manage-applications.md): where client IDs live diff --git a/en/docs/next/api-portal/mcp-servers/overview.md b/en/docs/next/api-portal/mcp-servers/overview.md index 5f9e7784e..0ca7cce30 100644 --- a/en/docs/next/api-portal/mcp-servers/overview.md +++ b/en/docs/next/api-portal/mcp-servers/overview.md @@ -31,10 +31,10 @@ An MCP server is stored as another artifact in the same catalog, so most of what | Consumed by | Application code you write | An MCP client, configured with the server's URL | | Agent visibility | Publisher sets it per API | Always agent-visible; the catalog marks every server **AI Ready** | -Everything else works the same way. MCP servers carry tags, labels, icons, subscription plans, and attached documents, appear in the portal's [machine-readable endpoints](../discover-apis/ai-agent-discovery.md), and are subscribed to directly rather than through an application. +Everything else works the same way. MCP servers carry tags, labels, icons, subscription plans, and attached documents, appear in the portal's [machine-readable endpoints](../ai-agent-discovery.md), and are subscribed to directly rather than through an application. !!! note - A portal only serves MCP servers when its operator lists `mcp-servers` in `enabled_types`. Leave it out and the sidebar entry, the catalog, and every MCP route disappear. See [Artifact types](../artifact-types.md). + A portal only serves MCP servers when its operator lists `mcp-servers` in `enabled_types`. Leave it out and the sidebar entry, the catalog, and every MCP route disappear. See [Artifact types](../setting-up/artifact-types.md). ## How servers reach the hub @@ -52,17 +52,17 @@ A client posts the server to the portal's registry API, an implementation of the Registry-published servers are identified by a reverse-DNS name such as `example.com/travel-assistant` rather than by a gateway reference, and they get one entry per version. Their generated Markdown documentation also omits the authentication walkthrough that other artifacts carry, because the registry payload doesn't describe a security scheme. -See [MCP Registry API](mcp-registry.md). +See [MCP Registry API](../mcp-registry.md). ## Where to go next - [Browse MCP Servers](browse-mcp-servers.md): browse the catalog, read a server's tools, and try them in the playground - [Connect to an MCP Server](connect-to-an-mcp-server.md): subscribe, get credentials, and point an MCP client at the server -- [MCP Registry API](mcp-registry.md): the discovery and publishing endpoints +- [MCP Registry API](../mcp-registry.md): the discovery and publishing endpoints ## Related - [Manage MCP Servers](../admin-settings/manage-mcp-servers.md): the admin side of adding and publishing servers -- [AI Agent Discovery](../discover-apis/ai-agent-discovery.md): the machine-readable endpoints that expose MCP servers to agents -- [Artifact types](../artifact-types.md): whether this deployment serves MCP servers +- [AI Agent Discovery](../ai-agent-discovery.md): the machine-readable endpoints that expose MCP servers to agents +- [Artifact types](../setting-up/artifact-types.md): whether this deployment serves MCP servers - [Concepts](../concepts.md): how MCP servers fit alongside APIs, plans, subscriptions, and keys diff --git a/en/docs/next/api-portal/overview.md b/en/docs/next/api-portal/overview.md index 2da627388..ff0e79689 100644 --- a/en/docs/next/api-portal/overview.md +++ b/en/docs/next/api-portal/overview.md @@ -30,31 +30,31 @@ Browse and search REST, GraphQL, SOAP, WebSocket, and WebSub APIs, as well as MC - Per-API documentation, landing page content, icons, and banners - Machine-readable discovery (`llms.txt`, per-API Markdown, OpenAPI/AsyncAPI specs) built for AI agent consumption -Learn more in [Discover APIs](discover-apis/browse-apis.md), [MCP Servers](mcp-servers/overview.md), and [AI Agent Discovery](discover-apis/ai-agent-discovery.md). +Learn more in [Discover APIs](discover-apis/browse-apis.md), [MCP Servers](mcp-servers/overview.md), and [AI Agent Discovery](ai-agent-discovery.md). ### MCP servers Publish Model Context Protocol servers alongside your APIs, each with its tools, resources, and prompts listed, an MCP Playground for invoking them, and a ready-made client configuration snippet. Servers arrive either through the admin UI or through the portal's implementation of the MCP registry specification. -Learn more in [MCP Servers](mcp-servers/overview.md) and the [MCP Registry API](mcp-servers/mcp-registry.md). +Learn more in [MCP Servers](mcp-servers/overview.md) and the [MCP Registry API](mcp-registry.md). ### Developer applications Logical containers for OAuth2 credentials. A developer can create multiple applications—for example, one per client or environment—each linked to independent OAuth2 client IDs. -Learn more in [Manage Applications](manage-applications.md). +Learn more in [Manage Applications](consume-an-api/manage-applications.md). ### Subscriptions and plans Developers subscribe directly to an API or MCP server under a named plan (for example, Gold or Bronze) that enforces rate limits and quotas. No application is required to subscribe. -Learn more in [Manage Subscriptions](manage-subscriptions.md) and, for admins, [Subscription Plans](admin-settings/subscription-plans.md). +Learn more in [Manage Subscriptions](consume-an-api/manage-subscriptions.md) and, for admins, [Subscription Plans](admin-settings/subscription-plans.md). ### API keys and OAuth2 credentials Generate, rotate, and revoke API keys bound to a specific API or MCP server. For OAuth2-secured APIs, link a client ID created in a key manager to an application and generate access tokens through the portal. -Learn more in [Manage API Keys](manage-api-keys.md), [Consume an API Secured with OAuth2](consume-an-api/oauth2.md), and, for admins, [Key Manager Integration](admin-settings/key-manager-integration.md). +Learn more in [Manage API Keys](consume-an-api/manage-api-keys.md), [Consume an API Secured with OAuth2](consume-an-api/oauth2.md), and, for admins, [Key Manager Integration](admin-settings/key-manager-integration.md). ### API workflows @@ -66,7 +66,7 @@ Learn more in [API Workflows](api-workflows.md). Give a view its own colors, page shell, header, footer, and page markup by uploading a theme. A theme is a partial copy of the default template tree, so a re-color is one file and everything you omit falls back to the default. -Learn more in [Theming](theming.md), and [Apply a Theme](admin-settings/theming.md) for the upload panel. +Learn more in [Theming](admin-settings/theming.md), and [Apply a Theme](admin-settings/theming.md) for the upload panel. ### Admin settings @@ -110,8 +110,8 @@ Beyond the Docker Compose quick start, a production deployment typically needs: | Topic | Covers | |---|---| | [Authentication](setting-up/authentication/overview.md) | Local and OIDC authentication modes, with an Asgardeo identity-provider walkthrough | -| [Artifact types](artifact-types.md) | Choose which artifact types—APIs, MCP servers, and API workflows—the portal serves | -| [Design Mode](setting-up/design-mode.md) | Develop and preview themes/layouts offline, without a database or IDP | +| [Artifact types](setting-up/artifact-types.md) | Choose which artifact types—APIs, MCP servers, and API workflows—the portal serves | +| [Design Mode](admin-settings/design-mode.md) | Develop and preview themes/layouts offline, without a database or IDP | ## References diff --git a/en/docs/next/api-portal/references/configurations.md b/en/docs/next/api-portal/references/configurations.md index 90f547c5f..5b912a989 100644 --- a/en/docs/next/api-portal/references/configurations.md +++ b/en/docs/next/api-portal/references/configurations.md @@ -210,7 +210,7 @@ Seeding runs on startup only if the organization doesn't already exist, so it's enabled_types = ["apis", "mcp-servers", "api-workflows"] ``` -An allowlist of the artifact types this portal serves. A type left out gets no navigation entry, no landing-page section, and `404`s on its routes. Valid entries are `apis`, `mcp-servers`, and `api-workflows`; an unrecognized entry aborts startup so a typo can't silently drop a type. Omit the section to serve all three. See [Artifact types](../artifact-types.md). +An allowlist of the artifact types this portal serves. A type left out gets no navigation entry, no landing-page section, and `404`s on its routes. Valid entries are `apis`, `mcp-servers`, and `api-workflows`; an unrecognized entry aborts startup so a typo can't silently drop a type. Omit the section to serve all three. See [Artifact types](../setting-up/artifact-types.md). ## Uploads @@ -227,7 +227,7 @@ max_depth = 10 These are the ceilings the Theming panel's "up to 10 MB" hint and the Manage APIs spec upload both derive from. `max_total_bytes`, `max_zip_entries`, and `max_depth` guard archive extraction against a decompression bomb, so raise them only deliberately. !!! note - This section isn't in `config-template.toml`—the values come from the built-in defaults. Add the table to `config.toml` to override them. + `config-template.toml` documents this table, but the shipped `config.toml` omits it—so the built-in defaults above apply until you add the table to `config.toml` yourself. ## Try-out proxy @@ -270,7 +270,7 @@ Two limits hold regardless of these settings: the proxy only calls URLs containe # applications_path = "./samples/applications.yaml" ``` -Disabled by default. See [Design Mode](../setting-up/design-mode.md) for the full field reference and theme-development workflow. +Disabled by default. See [Design Mode](../admin-settings/design-mode.md) for the full field reference and theme-development workflow. ## Webhooks @@ -288,8 +288,8 @@ Global delivery tuning only—subscribers themselves are per-organization, manag ## Related - [Authentication](../setting-up/authentication/overview.md) -- [Artifact types](../artifact-types.md) -- [Design Mode](../setting-up/design-mode.md) +- [Artifact types](../setting-up/artifact-types.md) +- [Design Mode](../admin-settings/design-mode.md) - [Webhook Event Catalog](webhook-event-catalog.md) - [Get a Bearer Token via curl](get-a-bearer-token-via-curl.md) - [Management API](../rest-api/overview.md) diff --git a/en/docs/next/api-portal/references/webhook-event-catalog.md b/en/docs/next/api-portal/references/webhook-event-catalog.md index 81d2e44ae..51cb51801 100644 --- a/en/docs/next/api-portal/references/webhook-event-catalog.md +++ b/en/docs/next/api-portal/references/webhook-event-catalog.md @@ -239,12 +239,12 @@ Three events cover an application's lifecycle: | `application.updated` | Its name or description changes | All of the above | | `application.deleted` | An application is deleted | `application_id`, `display_name`, `handle` | -Deleting an application also fires one `apikey.application_updated` per key that was associated with it, each with `application: null`. The keys themselves stay valid—see [Manage Applications](../manage-applications.md). +Deleting an application also fires one `apikey.application_updated` per key that was associated with it, each with `application: null`. The keys themselves stay valid—see [Manage Applications](../consume-an-api/manage-applications.md). ## Related - [Webhook Integration](../admin-settings/webhook-integration.md): register a subscriber and pick its events - [Webhook Subscribers](../rest-api/webhook-subscribers.md): manage subscribers through the Management API - [Webhook Events](../rest-api/webhook-events.md): read delivery history through the Management API -- [Manage API Keys](../manage-api-keys.md): the lifecycle behind the `apikey.*` events -- [Manage Subscriptions](../manage-subscriptions.md): the lifecycle behind the `subscription.*` events +- [Manage API Keys](../consume-an-api/manage-api-keys.md): the lifecycle behind the `apikey.*` events +- [Manage Subscriptions](../consume-an-api/manage-subscriptions.md): the lifecycle behind the `subscription.*` events diff --git a/en/docs/next/api-portal/artifact-types.md b/en/docs/next/api-portal/setting-up/artifact-types.md similarity index 95% rename from en/docs/next/api-portal/artifact-types.md rename to en/docs/next/api-portal/setting-up/artifact-types.md index e4061c29e..b2325660e 100644 --- a/en/docs/next/api-portal/artifact-types.md +++ b/en/docs/next/api-portal/setting-up/artifact-types.md @@ -1,8 +1,8 @@ --- title: "Artifact types" description: "Choose which artifact types—APIs, Model Context Protocol (MCP) servers, and API workflows—the API Portal & MCP Hub serves, via the api_portal.artifacts table in config.toml." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/artifact-types/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/artifact-types.md +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/artifact-types/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/artifact-types.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/tutorials/asgardeo-as-idp.md b/en/docs/next/api-portal/setting-up/authentication/asgardeo-as-idp.md similarity index 91% rename from en/docs/next/api-portal/tutorials/asgardeo-as-idp.md rename to en/docs/next/api-portal/setting-up/authentication/asgardeo-as-idp.md index 6bd8e8975..f11605dde 100644 --- a/en/docs/next/api-portal/tutorials/asgardeo-as-idp.md +++ b/en/docs/next/api-portal/setting-up/authentication/asgardeo-as-idp.md @@ -1,8 +1,8 @@ --- title: "Set up Asgardeo as your identity provider" description: "Configure WSO2 Asgardeo as the OIDC identity provider for a production API Portal deployment, from application registration to config.toml." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/tutorials/asgardeo-as-idp/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/tutorials/asgardeo-as-idp.md +canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/authentication/asgardeo-as-idp/ +md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/authentication/asgardeo-as-idp.md tags: - cloud - api-portal @@ -15,7 +15,7 @@ content_type: "tutorial" # Set up Asgardeo as your identity provider -This tutorial walks you through configuring WSO2 Asgardeo as the identity provider for a production API Portal deployment. It's one worked example of the general procedure in [Connect an identity provider to the API Portal](../setting-up/authentication/connect-an-identity-provider.md)—read that first if you're integrating a different provider. For how identity provider authentication compares with local authentication, see [Authentication in the API Portal & MCP Hub](../setting-up/authentication/overview.md). +This tutorial walks you through configuring WSO2 Asgardeo as the identity provider for a production API Portal deployment. It's one worked example of the general procedure in [Connect an identity provider to the API Portal](connect-an-identity-provider.md)—read that first if you're integrating a different provider. For how identity provider authentication compares with local authentication, see [Authentication in the API Portal & MCP Hub](overview.md). The API Portal & MCP Hub uses Asgardeo's sub-organization model: each API Portal organization maps to one Asgardeo sub-organization. A single Asgardeo application, shared across every portal organization, handles login, and each session is scoped to one sub-organization: @@ -66,7 +66,7 @@ The portal recognizes two personas: whoever administers it, and whoever consumes 2. Under the **Roles** tab, create an application role named `dp_admin` and another named `dp_subscriber`. 3. Assign `dp_admin` **only to administrators**, and `dp_subscriber` to regular users in each sub-organization that needs access. -Step 4 points both `[api_portal.auth.authorization.portal_roles]` entries at these same names, so one pair of Asgardeo roles drives both page access and Management API authorization. To change what either role grants, edit the portal's `role-to-scope-mapping.yaml`—see [Choose how privileges reach the token](../setting-up/authentication/connect-an-identity-provider.md#step-3-choose-how-privileges-reach-the-token). +Step 4 points both `[api_portal.auth.authorization.portal_roles]` entries at these same names, so one pair of Asgardeo roles drives both page access and Management API authorization. To change what either role grants, edit the portal's `role-to-scope-mapping.yaml`—see [Choose how privileges reach the token](connect-an-identity-provider.md#step-3-choose-how-privileges-reach-the-token). !!! note Browser login sessions still pass through the per-operation scope check in role mode, which is the gap role mode exists to close: the session's own roles claim is what the portal expands to authorize each Management API request. @@ -155,7 +155,7 @@ Two consequences worth knowing: - Protected pages (applications, subscriptions, API keys) need a token whose `org_name` matches, so a user reaching a different portal organization has to log out and log in again there. !!! note "If the handle can't match" - When the sub-org handle isn't a name you want in your portal URLs, the alternative is to have Asgardeo emit a separate claim carrying the portal handle as a constant, and map `organization` to that claim instead. Doing so drops the sub-organization distinction from the check—every sub-org's token then carries the same value—so choose it only for a tenant with a single sub-organization. See [when your IdP has no organization concept](../setting-up/authentication/connect-an-identity-provider.md#when-your-idp-has-no-organization-concept). + When the sub-org handle isn't a name you want in your portal URLs, the alternative is to have Asgardeo emit a separate claim carrying the portal handle as a constant, and map `organization` to that claim instead. Doing so drops the sub-organization distinction from the check—every sub-org's token then carries the same value—so choose it only for a tenant with a single sub-organization. See [when your IdP has no organization concept](connect-an-identity-provider.md#when-your-idp-has-no-organization-concept). ## Step 6: Restart and verify @@ -180,7 +180,7 @@ The Asgardeo token carries these claims through to the API Portal & MCP Hub: Keep the claim names consistent between the Asgardeo token attributes and the `[api_portal.auth.claim_mappings]` table. !!! important "Two retired keys abort startup" - Earlier versions configured roles under `[api_portal.auth.idp.roles]`, with a third `super_admin` tier. That section is retired, along with `auth.role_validation`, and leaving either in `config.toml` fails startup rather than silently applying a default. Use `[api_portal.auth.authorization.portal_roles]` and `auth.authorization.page_role_validation` instead—see [Authorization](../references/configurations.md#authorization). + Earlier versions configured roles under `[api_portal.auth.idp.roles]`, with a third `super_admin` tier. That section is retired, along with `auth.role_validation`, and leaving either in `config.toml` fails startup rather than silently applying a default. Use `[api_portal.auth.authorization.portal_roles]` and `auth.authorization.page_role_validation` instead—see [Authorization](../../references/configurations.md#authorization). ## Alternative: let Asgardeo mint the `dp:*` scopes @@ -212,7 +212,7 @@ In scope mode, browser sessions are preauthorized. For a user signed in through ## Related topics -- [Connect an identity provider to the API Portal](../setting-up/authentication/connect-an-identity-provider.md): the general procedure this tutorial is one example of -- [Authentication in the API Portal & MCP Hub](../setting-up/authentication/overview.md): how identity provider authentication compares with local authentication -- [Get a bearer token via curl](../references/get-a-bearer-token-via-curl.md): calling the Management API with a token from your IdP -- [Configurations](../references/configurations.md): every `config.toml` key +- [Connect an identity provider to the API Portal](connect-an-identity-provider.md): the general procedure this tutorial is one example of +- [Authentication in the API Portal & MCP Hub](overview.md): how identity provider authentication compares with local authentication +- [Get a bearer token via curl](../../references/get-a-bearer-token-via-curl.md): calling the Management API with a token from your IdP +- [Configurations](../../references/configurations.md): every `config.toml` key diff --git a/en/docs/next/api-portal/setting-up/authentication/connect-an-identity-provider.md b/en/docs/next/api-portal/setting-up/authentication/connect-an-identity-provider.md index 7b4df7a12..b870fdda7 100644 --- a/en/docs/next/api-portal/setting-up/authentication/connect-an-identity-provider.md +++ b/en/docs/next/api-portal/setting-up/authentication/connect-an-identity-provider.md @@ -15,7 +15,7 @@ content_type: "how-to" # Connect an identity provider to the API Portal -The API Portal & MCP Hub delegates user login to any identity provider (IdP) that speaks OpenID Connect (OIDC). This guide is written for the administrator who deploys the portal, and it covers the configuration every IdP needs. For a worked example of these steps against one specific IdP, see [Set up Asgardeo as your identity provider](../../tutorials/asgardeo-as-idp.md). +The API Portal & MCP Hub delegates user login to any identity provider (IdP) that speaks OpenID Connect (OIDC). This guide is written for the administrator who deploys the portal, and it covers the configuration every IdP needs. For a worked example of these steps against one specific IdP, see [Set up Asgardeo as your identity provider](asgardeo-as-idp.md). For how identity provider authentication compares with local authentication, see [Authentication in the API Portal & MCP Hub](overview.md). @@ -263,7 +263,7 @@ The roles claim is the one that most often differs. These paths are known to wor ## Related topics -- [Set up Asgardeo as your identity provider](../../tutorials/asgardeo-as-idp.md): these steps apply to Asgardeo, including `dp:*` scope registration +- [Set up Asgardeo as your identity provider](asgardeo-as-idp.md): these steps apply to Asgardeo, including `dp:*` scope registration - [Authentication in the API Portal & MCP Hub](overview.md): how identity provider authentication compares with local authentication - [Configurations](../../references/configurations.md): every `config.toml` key, and how interpolation tokens deliver values into it - [Organization settings](../../admin-settings/organization-settings.md): the organization's display name and references, as administrators see them diff --git a/en/docs/next/api-portal/setting-up/authentication/overview.md b/en/docs/next/api-portal/setting-up/authentication/overview.md index 3115b2d44..c1c4011f8 100644 --- a/en/docs/next/api-portal/setting-up/authentication/overview.md +++ b/en/docs/next/api-portal/setting-up/authentication/overview.md @@ -79,7 +79,7 @@ For production, configure the portal to delegate login to an identity provider ( When `mode = "idp"`, the portal reads the `[api_portal.auth.idp]` block for the OIDC endpoints and client credentials, and the `[api_portal.auth.claim_mappings]` block for the claim names that carry organization and role information. -[Connect an identity provider to the API Portal](connect-an-identity-provider.md) covers the configuration every IdP needs. For a worked example against one specific provider, see [Set up Asgardeo as your identity provider](../../tutorials/asgardeo-as-idp.md). +[Connect an identity provider to the API Portal](connect-an-identity-provider.md) covers the configuration every IdP needs. For a worked example against one specific provider, see [Set up Asgardeo as your identity provider](asgardeo-as-idp.md). ## Authorization is configured separately diff --git a/en/docs/next/api-portal/setting-up/configuration.md b/en/docs/next/api-portal/setting-up/configuration.md index abfcbd699..1fb33aeee 100644 --- a/en/docs/next/api-portal/setting-up/configuration.md +++ b/en/docs/next/api-portal/setting-up/configuration.md @@ -1,6 +1,6 @@ --- title: "API Portal configuration and environment interpolation" -description: "How the API Portal & MCP Hub and the Platform API load their config.toml files, inject environment values and mounted files through interpolation tokens, and keep sensitive values out of the config file." +description: "How the API Portal & MCP Hub loads its config.toml, injects environment values and mounted files through interpolation tokens, and keeps sensitive values out of the config file." canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/configuration/ md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/configuration.md tags: @@ -15,7 +15,7 @@ content_type: "reference" # API Portal configuration and environment interpolation -The API Portal stack has two services: the API Portal & MCP Hub itself and the Platform API control plane it authenticates against. Each reads its configuration from a TOML file (`config.toml`) layered over built-in defaults. +The API Portal & MCP Hub reads its configuration from a TOML file (`config.toml`) layered over built-in defaults. The quickstart runs it alongside a Platform API, which reads the same file, so both are covered here — a standalone production deployment needs only the portal's own section. This page explains how each service loads its config file. It also covers how environment values and mounted files are injected through interpolation tokens, and how to keep sensitive values out of the file. For the full reference of every supported key, see [Configurations](../references/configurations.md). For provisioning the keys, certificates, and credentials those tokens resolve to, see [Getting started](../getting-started.md). diff --git a/en/docs/next/api-portal/theming.md b/en/docs/next/api-portal/theming.md deleted file mode 100644 index 3c908a6f6..000000000 --- a/en/docs/next/api-portal/theming.md +++ /dev/null @@ -1,197 +0,0 @@ ---- -title: "Theming the API Portal & MCP Hub" -description: "Build a custom theme for a view—override styles, layouts, partials, and pages, re-color the portal from a few seed variables, and package it for upload." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/theming/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/theming.md -tags: - - cloud - - api-portal - - theming -author: WSO2 API Platform Documentation Team -last_updated: 2026-07-31 -content_type: "how-to" ---- - -# Theming - -A theme changes how a [view](admin-settings/manage-views.md) looks and behaves: its colors, page shell, header and footer, and the markup of individual pages. You build one as a directory of files, package it as a ZIP, and an admin applies it to a view. - -Theming is scoped **per view**, so one organization can serve a branded partner portal and a plain internal one from the same catalog. - -## How a theme works - -The portal renders every page from a complete default template tree at `src/defaultContent/`. A theme is a **partial copy of that tree**: include only the files you want to change, and every file you leave out is served from the default automatically. - -That has two consequences worth internalizing: - -- **A theme is not just colors.** Any file under `src/defaultContent` can be overridden—this includes the page shell, partials, and the markup of individual pages. -- **You never fork the whole tree.** A color-only theme is one file. Adding a custom API listing is two. - -### What you can override - -These are the files the example theme replaces, and what each one governs: - -| Path in the theme | Controls | -|---|---| -| `styles/main.css` | The portal-wide stylesheet, including the color tokens | -| `layout/main.hbs` | The outer HTML shell—`<head>`, page `<title>`, nav frame | -| `partials/header.hbs` | The top bar | -| `partials/footer.hbs` | The footer | -| `pages/home/partials/home.hbs` | The home page body | -| `pages/apis/partials/api-listing.hbs` | How APIs are listed, including the card markup | -| `pages/api-landing/partials/api-detail-banner.hbs` | The header block on an API's overview page | -| `images/` | Logo and other image assets. Optional, and the example below ships none | - -Any other `pages/**/partials/*.hbs` file from the default tree works the same way—those are just the ones the example below uses. - -!!! note - JavaScript can't be added as a theme asset, and templates are validated on upload. Portal behavior comes from the portal's own scripts; a theme covers CSS, Handlebars templates, and images. - -## Colors come from a few seeds - -`styles/main.css` defines its palette as a small set of **seed** variables, with everything else **derived** from them through `color-mix()`. Change the seeds and the whole portal re-colors—text ramp, borders, surfaces, gradients, the dark hero and sidebar included. - -The seeds are: - -```css -:root { - /* SEEDS · brand — these cascade everywhere */ - --primary: #1a4c6d; /* structure, links, focus, primary buttons */ - --primary-dark: #043556; /* primary hover / pressed / deep panels */ - --primary-light: #2b719f; /* lifted primary states, on-dark highlights */ - --accent: #fe8c3a; /* solid highlights, badges, env dots, borders */ - --accent-dark: #ef4223; - --accent-light: #ff8636; - - /* SEEDS · neutral foundation */ - --ink: #1a2433; /* darkest text — generates the whole grey ramp */ - --surface: #ffffff; /* page background — the ramp mixes toward this */ - --white: #ffffff; /* fills on colored or dark surfaces */ - - /* SEEDS · semantic — deliberately independent of the brand */ - --success: #2e7d32; - --warning: #e39a00; - --danger: #c62828; - --info: #0277bd; -} -``` - -Everything below them in the file is derived, for example: - -```css ---text: color-mix(in srgb, var(--ink) 80%, var(--surface)); ---border: color-mix(in srgb, var(--ink) 12%, var(--surface)); ---focus-ring: color-mix(in srgb, var(--primary) 40%, transparent); -``` - -So a re-color means editing the seed block and nothing else. - -!!! important "Copy `main.css` whole" - Don't replace `main.css` with just a `:root` override. It carries both the variables *and* the rule definitions, plus the `@import` statements that pull in the other stylesheets (`home.css`, `header.css`, and the rest). Those imported files are served from the defaults and don't need copying—but the file that imports them does. - -## Example: a teal and coral theme - -Here's a complete theme that re-colors the portal and replaces the wordmark, home hero, and API listing. It's seven files: - -```text -my-theme/ -├── styles/main.css # seeds changed, plus a few custom rules -├── layout/main.hbs # page shell and <title> -├── partials/header.hbs # text wordmark instead of the logo image -├── partials/footer.hbs # custom footer links -└── pages/ - ├── home/partials/home.hbs # custom hero copy - ├── apis/partials/api-listing.hbs # hero, plus a different card type and arrangement - └── api-landing/partials/api-detail-banner.hbs # custom API overview header -``` - -Everything else—the other pages, the sidebar, the imported stylesheets—is served from the defaults untouched. - -### The color change - -`styles/main.css` is a copy of the default with its seed block swapped. That single edit is the whole re-color: - -```css -:root { - /* SEEDS · brand — teal and coral instead of the default navy and orange */ - --primary: #0f766e; - --primary-dark: #134e4a; - --primary-light: #2dd4bf; - --accent: #fb7185; - --accent-dark: #e11d48; - --accent-light: #fda4af; - - /* SEEDS · neutral foundation — --ink generates the whole grey ramp */ - --ink: #0e2a2a; - --surface: #ffffff; - --white: #ffffff; -} -``` - -The dark hero, the sidebar gradient, borders, and the text ramp all follow from those, because they're derived rather than hardcoded. - -### Replacing the wordmark - -`partials/header.hbs` swaps the logo image for text and an icon, which shows that branding doesn't require shipping a new asset: - -```handlebars -<a class="navbar-brand d-flex align-items-center brand-wordmark" href="{{baseUrl}}"> - <i class="bi bi-water brand-wordmark-icon" aria-hidden="true"></i> - <span class="brand-wordmark-text">Green</span> -</a> -``` - -The classes it introduces are styled by a small block appended to `main.css`, alongside the custom listing and overview treatments. - -### Starting from it - -A working copy of this theme sits at [`samples/layouts/green-theme/`](https://github.com/wso2/api-platform/tree/main/portals/api-portal/samples/layouts/green-theme) in the API Platform repository, and at the same path in the portal distribution. Copy it, change the seeds, and delete any override you don't want: - -```bash -cp -r samples/layouts/green-theme/ my-theme/ -``` - -## Build and preview - -[Design Mode](setting-up/design-mode.md) is the fastest way to iterate: it serves the portal from a theme directory on disk with sample data, no database or identity provider needed, and picks up file edits on reload. - -Point it at your theme directory: - -```toml -[api_portal.design_mode] -enabled = true -path_to_layout = "./my-theme/" -``` - -To preview the example above before editing it, point `path_to_layout` at `./samples/layouts/green-theme/`. - -## Package the theme - -The ZIP must contain **one wrapper directory** holding the theme, not the theme's folders at the root. Zip the directory from its parent: - -```bash -zip -r my-theme.zip my-theme/ -``` - -That produces `my-theme/styles/main.css`, `my-theme/layout/main.hbs`, and so on. - -!!! warning "A wrapper directory is required" - The portal classifies each file by the path *below* the first segment. Zipping from inside the theme directory—so that `layout/` and `styles/` sit at the ZIP root—makes `layout/main.hbs` register as a generic template rather than the page shell, and your layout silently won't apply. - -Uploads are size-limited; see `uploads.max_bytes` in [Configurations](references/configurations.md#uploads). - -## Apply it - -An admin uploads the ZIP to a view under **Settings** → **Theming**. Applying a theme replaces that view's existing theme files entirely rather than merging with them. See [Apply a Theme](admin-settings/theming.md) for the panel, and for downloading the current theme or resetting to the default. - -## Theming vs. API content - -A theme applies to every page in a view. To change one API's overview body—or set its icon, or attach documents—you upload content against that API instead, which leaves the rest of the view alone. See [Customize an API's Content](admin-settings/api-content.md). - -## Related - -- [Apply a Theme](admin-settings/theming.md): upload, download, and reset a view's theme -- [Design Mode](setting-up/design-mode.md): preview a theme offline against sample data -- [Manage Views](admin-settings/manage-views.md): why themes are per-view -- [Customize an API's Content](admin-settings/api-content.md): change one API's overview body instead of the whole view -- [Configurations](references/configurations.md#uploads): upload size limits diff --git a/en/docs/next/api-portal/tutorials/secured-api-end-to-end.md b/en/docs/next/api-portal/tutorials/secured-api-end-to-end.md index 33bb93434..eae3a3057 100644 --- a/en/docs/next/api-portal/tutorials/secured-api-end-to-end.md +++ b/en/docs/next/api-portal/tutorials/secured-api-end-to-end.md @@ -455,7 +455,7 @@ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/reading-list/v1/b Once the webhook lands you get **401**—the key is gone from the gateway without anyone touching the gateway. Generate a new key and the call succeeds again. -The same holds for the subscription side, where a rejection reads as **403** rather than 401: suspending the subscription blocks calls, resuming restores them, and regenerating the token invalidates the old one while the new one works. See [Manage Subscriptions](../manage-subscriptions.md). +The same holds for the subscription side, where a rejection reads as **403** rather than 401: suspending the subscription blocks calls, resuming restores them, and regenerating the token invalidates the old one while the new one works. See [Manage Subscriptions](../consume-an-api/manage-subscriptions.md). ## Troubleshooting @@ -474,5 +474,5 @@ The same holds for the subscription side, where a rejection reads as **403** rat - [Webhook Event Catalog](../references/webhook-event-catalog.md): the events this flow depends on, their payloads, and the signing and encryption scheme - [Webhook Integration](../admin-settings/webhook-integration.md): registering a subscriber from the Settings UI - [Consume an API](../consume-an-api/overview.md): which credentials an API expects, and how to tell -- [Manage Subscriptions](../manage-subscriptions.md) and [Manage API Keys](../manage-api-keys.md): the consumer-side lifecycles +- [Manage Subscriptions](../consume-an-api/manage-subscriptions.md) and [Manage API Keys](../consume-an-api/manage-api-keys.md): the consumer-side lifecycles - [Getting Started](../getting-started.md): the portal on its own, without a gateway diff --git a/en/mkdocs.yml b/en/mkdocs.yml index 611e1dbd4..dd17fd456 100644 --- a/en/mkdocs.yml +++ b/en/mkdocs.yml @@ -701,9 +701,8 @@ nav: - Authentication: - Overview: next/api-portal/setting-up/authentication/overview.md - Connect an Identity Provider: next/api-portal/setting-up/authentication/connect-an-identity-provider.md - - Set up Asgardeo: next/api-portal/tutorials/asgardeo-as-idp.md - - Artifact Types: next/api-portal/artifact-types.md - - Design Mode: next/api-portal/setting-up/design-mode.md + - Set up Asgardeo: next/api-portal/setting-up/authentication/asgardeo-as-idp.md + - Artifact Types: next/api-portal/setting-up/artifact-types.md # What a consumer does in the portal, in the order they do it. - Discover APIs: - Overview: next/api-portal/discover-apis/overview.md @@ -713,13 +712,13 @@ nav: - Browse MCP Servers: next/api-portal/mcp-servers/browse-mcp-servers.md - Connect to an MCP Server: next/api-portal/mcp-servers/connect-to-an-mcp-server.md - API Workflows: next/api-portal/api-workflows.md - - AI Agent Discovery: next/api-portal/discover-apis/ai-agent-discovery.md - - MCP Registry API: next/api-portal/mcp-servers/mcp-registry.md + - AI Agent Discovery: next/api-portal/ai-agent-discovery.md + - MCP Registry API: next/api-portal/mcp-registry.md - Applications and Credentials: - Which Credentials You Need: next/api-portal/consume-an-api/overview.md - - Manage Applications: next/api-portal/manage-applications.md - - Manage Subscriptions: next/api-portal/manage-subscriptions.md - - Manage API Keys: next/api-portal/manage-api-keys.md + - Manage Applications: next/api-portal/consume-an-api/manage-applications.md + - Manage Subscriptions: next/api-portal/consume-an-api/manage-subscriptions.md + - Manage API Keys: next/api-portal/consume-an-api/manage-api-keys.md - Call an OAuth2-Secured API: next/api-portal/consume-an-api/oauth2.md - Call an API-Key-Secured API: next/api-portal/consume-an-api/api-key.md # Configure the portal at runtime, once it is serving. @@ -739,8 +738,9 @@ nav: - Key Manager Integration: next/api-portal/admin-settings/key-manager-integration.md - Webhook Integration: next/api-portal/admin-settings/webhook-integration.md - Appearance: - - Theming: next/api-portal/theming.md - - Apply a Theme to a View: next/api-portal/admin-settings/theming.md + - Theming: next/api-portal/admin-settings/theming.md + - Apply a Theme to a View: next/api-portal/admin-settings/apply-a-theme.md + - Design Mode: next/api-portal/admin-settings/design-mode.md - References: - Management API: - Overview: next/api-portal/rest-api/overview.md @@ -965,6 +965,16 @@ plugins: # API Portal: overview + documentation pages merged into Browse APIs next/api-portal/discover-apis/api-overview.md: next/api-portal/discover-apis/browse-apis.md next/api-portal/discover-apis/api-documentations.md: next/api-portal/discover-apis/browse-apis.md + # API Portal: files relocated to match their nav sections + next/api-portal/discover-apis/ai-agent-discovery.md: next/api-portal/ai-agent-discovery.md + next/api-portal/mcp-servers/mcp-registry.md: next/api-portal/mcp-registry.md + next/api-portal/theming.md: next/api-portal/admin-settings/theming.md + next/api-portal/artifact-types.md: next/api-portal/setting-up/artifact-types.md + next/api-portal/manage-applications.md: next/api-portal/consume-an-api/manage-applications.md + next/api-portal/manage-subscriptions.md: next/api-portal/consume-an-api/manage-subscriptions.md + next/api-portal/manage-api-keys.md: next/api-portal/consume-an-api/manage-api-keys.md + next/api-portal/setting-up/design-mode.md: next/api-portal/admin-settings/design-mode.md + next/api-portal/tutorials/asgardeo-as-idp.md: next/api-portal/setting-up/authentication/asgardeo-as-idp.md # API Portal: filenames aligned with page titles next/api-portal/discover-apis/api-search.md: next/api-portal/discover-apis/browse-apis.md next/api-portal/mcp-servers/discover-mcp-servers.md: next/api-portal/mcp-servers/browse-mcp-servers.md From 6f5cd33a035a4482246ee828c4c1e5e9f5b3f2f6 Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake <piumal1999@gmail.com> Date: Fri, 7 Aug 2026 11:45:55 +0530 Subject: [PATCH 08/11] Move api-portal docs to root level --- .../1.0.0}/about-this-release.md | 4 +- .../1.0.0}/admin-settings/api-content.md | 4 +- .../1.0.0}/admin-settings/apply-a-theme.md | 4 +- .../1.0.0}/admin-settings/design-mode.md | 4 +- .../admin-settings/key-manager-integration.md | 4 +- .../1.0.0}/admin-settings/llm-instructions.md | 4 +- .../admin-settings/manage-api-workflows.md | 4 +- .../1.0.0}/admin-settings/manage-apis.md | 4 +- .../1.0.0}/admin-settings/manage-labels.md | 4 +- .../admin-settings/manage-mcp-servers.md | 4 +- .../1.0.0}/admin-settings/manage-views.md | 4 +- .../admin-settings/organization-settings.md | 4 +- .../admin-settings/subscription-plans.md | 4 +- .../1.0.0}/admin-settings/theming.md | 4 +- .../admin-settings/webhook-integration.md | 4 +- .../1.0.0}/ai-agent-discovery.md | 4 +- .../1.0.0}/api-workflows.md | 4 +- .../1.0.0}/concepts.md | 4 +- .../1.0.0}/consume-an-api/api-key.md | 4 +- .../1.0.0}/consume-an-api/manage-api-keys.md | 4 +- .../consume-an-api/manage-applications.md | 4 +- .../consume-an-api/manage-subscriptions.md | 4 +- .../1.0.0}/consume-an-api/oauth2.md | 4 +- .../1.0.0}/consume-an-api/overview.md | 4 +- .../1.0.0}/discover-apis/browse-apis.md | 4 +- .../1.0.0}/discover-apis/overview.md | 4 +- .../1.0.0}/getting-started.md | 4 +- .../1.0.0}/mcp-registry.md | 4 +- .../1.0.0}/mcp-servers/browse-mcp-servers.md | 4 +- .../mcp-servers/connect-to-an-mcp-server.md | 4 +- .../1.0.0}/mcp-servers/overview.md | 4 +- .../1.0.0}/overview.md | 4 +- .../1.0.0}/references/configurations.md | 4 +- .../references/get-a-bearer-token-via-curl.md | 4 +- .../references/webhook-event-catalog.md | 4 +- .../1.0.0}/rest-api/api-content.md | 4 +- .../1.0.0}/rest-api/api-keys.md | 4 +- .../1.0.0}/rest-api/api-workflows.md | 4 +- .../1.0.0}/rest-api/apis.md | 4 +- .../1.0.0}/rest-api/application-keys.md | 4 +- .../1.0.0}/rest-api/applications.md | 4 +- .../1.0.0}/rest-api/authentication.md | 4 +- .../1.0.0}/rest-api/key-managers.md | 4 +- .../1.0.0}/rest-api/labels.md | 4 +- .../1.0.0}/rest-api/mcp-server-content.md | 4 +- .../1.0.0}/rest-api/mcp-server-keys.md | 4 +- .../1.0.0}/rest-api/mcp-servers.md | 4 +- .../1.0.0}/rest-api/organization-content.md | 4 +- .../1.0.0}/rest-api/organizations.md | 4 +- .../1.0.0}/rest-api/overview.md | 4 +- .../1.0.0}/rest-api/schemas.md | 4 +- .../1.0.0}/rest-api/subscription-plans.md | 4 +- .../1.0.0}/rest-api/subscriptions.md | 4 +- .../1.0.0}/rest-api/views.md | 4 +- .../1.0.0}/rest-api/webhook-events.md | 4 +- .../1.0.0}/rest-api/webhook-subscribers.md | 4 +- .../1.0.0}/setting-up/artifact-types.md | 4 +- .../authentication/asgardeo-as-idp.md | 4 +- .../connect-an-identity-provider.md | 4 +- .../setting-up/authentication/overview.md | 4 +- .../1.0.0}/setting-up/configuration.md | 4 +- .../1.0.0}/setting-up/database.md | 4 +- .../1.0.0}/setting-up/ports.md | 4 +- .../tutorials/secured-api-end-to-end.md | 4 +- en/docs/api-portal/next/about-this-release.md | 164 ++ .../next/admin-settings/api-content.md | 121 + .../next/admin-settings/apply-a-theme.md | 54 + .../next/admin-settings/design-mode.md | 233 ++ .../admin-settings/key-manager-integration.md | 45 + .../next/admin-settings/llm-instructions.md | 79 + .../admin-settings/manage-api-workflows.md | 113 + .../next/admin-settings/manage-apis.md | 86 + .../next/admin-settings/manage-labels.md | 42 + .../next/admin-settings/manage-mcp-servers.md | 60 + .../next/admin-settings/manage-views.md | 45 + .../admin-settings/organization-settings.md | 39 + .../next/admin-settings/subscription-plans.md | 56 + .../api-portal/next/admin-settings/theming.md | 197 ++ .../admin-settings/webhook-integration.md | 75 + en/docs/api-portal/next/ai-agent-discovery.md | 132 ++ en/docs/api-portal/next/api-workflows.md | 110 + en/docs/api-portal/next/concepts.md | 119 + .../api-portal/next/consume-an-api/api-key.md | 64 + .../next/consume-an-api/manage-api-keys.md | 98 + .../consume-an-api/manage-applications.md | 87 + .../consume-an-api/manage-subscriptions.md | 91 + .../api-portal/next/consume-an-api/oauth2.md | 112 + .../next/consume-an-api/overview.md | 69 + .../next/discover-apis/browse-apis.md | 109 + .../api-portal/next/discover-apis/overview.md | 77 + en/docs/api-portal/next/getting-started.md | 253 ++ en/docs/api-portal/next/mcp-registry.md | 222 ++ .../next/mcp-servers/browse-mcp-servers.md | 91 + .../mcp-servers/connect-to-an-mcp-server.md | 91 + .../api-portal/next/mcp-servers/overview.md | 68 + en/docs/api-portal/next/overview.md | 128 ++ .../next/references/configurations.md | 295 +++ .../references/get-a-bearer-token-via-curl.md | 154 ++ .../next/references/webhook-event-catalog.md | 250 ++ .../api-portal/next/rest-api/api-content.md | 446 ++++ en/docs/api-portal/next/rest-api/api-keys.md | 959 ++++++++ .../api-portal/next/rest-api/api-workflows.md | 481 ++++ en/docs/api-portal/next/rest-api/apis.md | 784 +++++++ .../next/rest-api/application-keys.md | 329 +++ .../api-portal/next/rest-api/applications.md | 531 +++++ .../next/rest-api/authentication.md | 112 + .../api-portal/next/rest-api/key-managers.md | 545 +++++ en/docs/api-portal/next/rest-api/labels.md | 494 ++++ .../next/rest-api/mcp-server-content.md | 431 ++++ .../next/rest-api/mcp-server-keys.md | 708 ++++++ .../api-portal/next/rest-api/mcp-servers.md | 735 ++++++ .../next/rest-api/organization-content.md | 310 +++ .../api-portal/next/rest-api/organizations.md | 522 +++++ en/docs/api-portal/next/rest-api/overview.md | 177 ++ en/docs/api-portal/next/rest-api/schemas.md | 2045 +++++++++++++++++ .../next/rest-api/subscription-plans.md | 712 ++++++ .../api-portal/next/rest-api/subscriptions.md | 724 ++++++ en/docs/api-portal/next/rest-api/views.md | 548 +++++ .../next/rest-api/webhook-events.md | 267 +++ .../next/rest-api/webhook-subscribers.md | 656 ++++++ .../next/setting-up/artifact-types.md | 65 + .../authentication/asgardeo-as-idp.md | 218 ++ .../connect-an-identity-provider.md | 269 +++ .../setting-up/authentication/overview.md | 97 + .../next/setting-up/configuration.md | 113 + .../api-portal/next/setting-up/database.md | 191 ++ en/docs/api-portal/next/setting-up/ports.md | 165 ++ .../next/tutorials/secured-api-end-to-end.md | 478 ++++ en/mkdocs.yml | 289 ++- 129 files changed, 18456 insertions(+), 230 deletions(-) rename en/docs/{next/api-portal => api-portal/1.0.0}/about-this-release.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/api-content.md (96%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/apply-a-theme.md (93%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/design-mode.md (97%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/key-manager-integration.md (92%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/llm-instructions.md (95%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/manage-api-workflows.md (96%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/manage-apis.md (95%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/manage-labels.md (88%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/manage-mcp-servers.md (93%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/manage-views.md (90%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/organization-settings.md (92%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/subscription-plans.md (93%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/theming.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/admin-settings/webhook-integration.md (96%) rename en/docs/{next/api-portal => api-portal/1.0.0}/ai-agent-discovery.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/api-workflows.md (97%) rename en/docs/{next/api-portal => api-portal/1.0.0}/concepts.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/consume-an-api/api-key.md (95%) rename en/docs/{next/api-portal => api-portal/1.0.0}/consume-an-api/manage-api-keys.md (96%) rename en/docs/{next/api-portal => api-portal/1.0.0}/consume-an-api/manage-applications.md (96%) rename en/docs/{next/api-portal => api-portal/1.0.0}/consume-an-api/manage-subscriptions.md (95%) rename en/docs/{next/api-portal => api-portal/1.0.0}/consume-an-api/oauth2.md (96%) rename en/docs/{next/api-portal => api-portal/1.0.0}/consume-an-api/overview.md (96%) rename en/docs/{next/api-portal => api-portal/1.0.0}/discover-apis/browse-apis.md (97%) rename en/docs/{next/api-portal => api-portal/1.0.0}/discover-apis/overview.md (95%) rename en/docs/{next/api-portal => api-portal/1.0.0}/getting-started.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/mcp-registry.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/mcp-servers/browse-mcp-servers.md (96%) rename en/docs/{next/api-portal => api-portal/1.0.0}/mcp-servers/connect-to-an-mcp-server.md (95%) rename en/docs/{next/api-portal => api-portal/1.0.0}/mcp-servers/overview.md (95%) rename en/docs/{next/api-portal => api-portal/1.0.0}/overview.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/references/configurations.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/references/get-a-bearer-token-via-curl.md (96%) rename en/docs/{next/api-portal => api-portal/1.0.0}/references/webhook-event-catalog.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/api-content.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/api-keys.md (99%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/api-workflows.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/apis.md (99%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/application-keys.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/applications.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/authentication.md (96%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/key-managers.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/labels.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/mcp-server-content.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/mcp-server-keys.md (99%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/mcp-servers.md (99%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/organization-content.md (97%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/organizations.md (99%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/overview.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/schemas.md (99%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/subscription-plans.md (99%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/subscriptions.md (99%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/views.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/webhook-events.md (97%) rename en/docs/{next/api-portal => api-portal/1.0.0}/rest-api/webhook-subscribers.md (99%) rename en/docs/{next/api-portal => api-portal/1.0.0}/setting-up/artifact-types.md (92%) rename en/docs/{next/api-portal => api-portal/1.0.0}/setting-up/authentication/asgardeo-as-idp.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/setting-up/authentication/connect-an-identity-provider.md (98%) rename en/docs/{next/api-portal => api-portal/1.0.0}/setting-up/authentication/overview.md (96%) rename en/docs/{next/api-portal => api-portal/1.0.0}/setting-up/configuration.md (97%) rename en/docs/{next/api-portal => api-portal/1.0.0}/setting-up/database.md (97%) rename en/docs/{next/api-portal => api-portal/1.0.0}/setting-up/ports.md (97%) rename en/docs/{next/api-portal => api-portal/1.0.0}/tutorials/secured-api-end-to-end.md (99%) create mode 100644 en/docs/api-portal/next/about-this-release.md create mode 100644 en/docs/api-portal/next/admin-settings/api-content.md create mode 100644 en/docs/api-portal/next/admin-settings/apply-a-theme.md create mode 100644 en/docs/api-portal/next/admin-settings/design-mode.md create mode 100644 en/docs/api-portal/next/admin-settings/key-manager-integration.md create mode 100644 en/docs/api-portal/next/admin-settings/llm-instructions.md create mode 100644 en/docs/api-portal/next/admin-settings/manage-api-workflows.md create mode 100644 en/docs/api-portal/next/admin-settings/manage-apis.md create mode 100644 en/docs/api-portal/next/admin-settings/manage-labels.md create mode 100644 en/docs/api-portal/next/admin-settings/manage-mcp-servers.md create mode 100644 en/docs/api-portal/next/admin-settings/manage-views.md create mode 100644 en/docs/api-portal/next/admin-settings/organization-settings.md create mode 100644 en/docs/api-portal/next/admin-settings/subscription-plans.md create mode 100644 en/docs/api-portal/next/admin-settings/theming.md create mode 100644 en/docs/api-portal/next/admin-settings/webhook-integration.md create mode 100644 en/docs/api-portal/next/ai-agent-discovery.md create mode 100644 en/docs/api-portal/next/api-workflows.md create mode 100644 en/docs/api-portal/next/concepts.md create mode 100644 en/docs/api-portal/next/consume-an-api/api-key.md create mode 100644 en/docs/api-portal/next/consume-an-api/manage-api-keys.md create mode 100644 en/docs/api-portal/next/consume-an-api/manage-applications.md create mode 100644 en/docs/api-portal/next/consume-an-api/manage-subscriptions.md create mode 100644 en/docs/api-portal/next/consume-an-api/oauth2.md create mode 100644 en/docs/api-portal/next/consume-an-api/overview.md create mode 100644 en/docs/api-portal/next/discover-apis/browse-apis.md create mode 100644 en/docs/api-portal/next/discover-apis/overview.md create mode 100644 en/docs/api-portal/next/getting-started.md create mode 100644 en/docs/api-portal/next/mcp-registry.md create mode 100644 en/docs/api-portal/next/mcp-servers/browse-mcp-servers.md create mode 100644 en/docs/api-portal/next/mcp-servers/connect-to-an-mcp-server.md create mode 100644 en/docs/api-portal/next/mcp-servers/overview.md create mode 100644 en/docs/api-portal/next/overview.md create mode 100644 en/docs/api-portal/next/references/configurations.md create mode 100644 en/docs/api-portal/next/references/get-a-bearer-token-via-curl.md create mode 100644 en/docs/api-portal/next/references/webhook-event-catalog.md create mode 100644 en/docs/api-portal/next/rest-api/api-content.md create mode 100644 en/docs/api-portal/next/rest-api/api-keys.md create mode 100644 en/docs/api-portal/next/rest-api/api-workflows.md create mode 100644 en/docs/api-portal/next/rest-api/apis.md create mode 100644 en/docs/api-portal/next/rest-api/application-keys.md create mode 100644 en/docs/api-portal/next/rest-api/applications.md create mode 100644 en/docs/api-portal/next/rest-api/authentication.md create mode 100644 en/docs/api-portal/next/rest-api/key-managers.md create mode 100644 en/docs/api-portal/next/rest-api/labels.md create mode 100644 en/docs/api-portal/next/rest-api/mcp-server-content.md create mode 100644 en/docs/api-portal/next/rest-api/mcp-server-keys.md create mode 100644 en/docs/api-portal/next/rest-api/mcp-servers.md create mode 100644 en/docs/api-portal/next/rest-api/organization-content.md create mode 100644 en/docs/api-portal/next/rest-api/organizations.md create mode 100644 en/docs/api-portal/next/rest-api/overview.md create mode 100644 en/docs/api-portal/next/rest-api/schemas.md create mode 100644 en/docs/api-portal/next/rest-api/subscription-plans.md create mode 100644 en/docs/api-portal/next/rest-api/subscriptions.md create mode 100644 en/docs/api-portal/next/rest-api/views.md create mode 100644 en/docs/api-portal/next/rest-api/webhook-events.md create mode 100644 en/docs/api-portal/next/rest-api/webhook-subscribers.md create mode 100644 en/docs/api-portal/next/setting-up/artifact-types.md create mode 100644 en/docs/api-portal/next/setting-up/authentication/asgardeo-as-idp.md create mode 100644 en/docs/api-portal/next/setting-up/authentication/connect-an-identity-provider.md create mode 100644 en/docs/api-portal/next/setting-up/authentication/overview.md create mode 100644 en/docs/api-portal/next/setting-up/configuration.md create mode 100644 en/docs/api-portal/next/setting-up/database.md create mode 100644 en/docs/api-portal/next/setting-up/ports.md create mode 100644 en/docs/api-portal/next/tutorials/secured-api-end-to-end.md diff --git a/en/docs/next/api-portal/about-this-release.md b/en/docs/api-portal/1.0.0/about-this-release.md similarity index 98% rename from en/docs/next/api-portal/about-this-release.md rename to en/docs/api-portal/1.0.0/about-this-release.md index fea51d8db..88fe1c151 100644 --- a/en/docs/next/api-portal/about-this-release.md +++ b/en/docs/api-portal/1.0.0/about-this-release.md @@ -1,8 +1,8 @@ --- title: "About this release" description: "What's included in API Portal & MCP Hub 1.0.0: the API and MCP catalog, API workflows, MCP registry, applications, authentication, theming, and the admin UI." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/about-this-release/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/about-this-release.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/about-this-release/ +md_url: https://wso2.com/api-platform/docs/api-portal/about-this-release.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/api-content.md b/en/docs/api-portal/1.0.0/admin-settings/api-content.md similarity index 96% rename from en/docs/next/api-portal/admin-settings/api-content.md rename to en/docs/api-portal/1.0.0/admin-settings/api-content.md index dc34c6ca4..275f8b26d 100644 --- a/en/docs/next/api-portal/admin-settings/api-content.md +++ b/en/docs/api-portal/1.0.0/admin-settings/api-content.md @@ -1,8 +1,8 @@ --- title: "Customize an API's content" description: "Replace an API's generated overview page with your own Markdown or Handlebars body, set its icon, and attach downloadable documents." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/api-content/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/api-content.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/api-content/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/api-content.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/apply-a-theme.md b/en/docs/api-portal/1.0.0/admin-settings/apply-a-theme.md similarity index 93% rename from en/docs/next/api-portal/admin-settings/apply-a-theme.md rename to en/docs/api-portal/1.0.0/admin-settings/apply-a-theme.md index fa66c0762..71b501cc1 100644 --- a/en/docs/next/api-portal/admin-settings/apply-a-theme.md +++ b/en/docs/api-portal/1.0.0/admin-settings/apply-a-theme.md @@ -1,8 +1,8 @@ --- title: "Apply a theme to a view" description: "Upload a theme ZIP to a view in the API Portal & MCP Hub, download the current theme, or reset to the built-in default." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/apply-a-theme/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/apply-a-theme.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/apply-a-theme/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/apply-a-theme.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/design-mode.md b/en/docs/api-portal/1.0.0/admin-settings/design-mode.md similarity index 97% rename from en/docs/next/api-portal/admin-settings/design-mode.md rename to en/docs/api-portal/1.0.0/admin-settings/design-mode.md index 380c4d0fe..3c7a2b02b 100644 --- a/en/docs/next/api-portal/admin-settings/design-mode.md +++ b/en/docs/api-portal/1.0.0/admin-settings/design-mode.md @@ -1,8 +1,8 @@ --- title: "Design Mode" description: "Develop and preview API Portal layouts and themes offline, without a running database or identity provider." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/design-mode/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/design-mode.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/design-mode/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/design-mode.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/key-manager-integration.md b/en/docs/api-portal/1.0.0/admin-settings/key-manager-integration.md similarity index 92% rename from en/docs/next/api-portal/admin-settings/key-manager-integration.md rename to en/docs/api-portal/1.0.0/admin-settings/key-manager-integration.md index d1bf67ec4..0974bcccc 100644 --- a/en/docs/next/api-portal/admin-settings/key-manager-integration.md +++ b/en/docs/api-portal/1.0.0/admin-settings/key-manager-integration.md @@ -1,8 +1,8 @@ --- title: "Configure key managers in the API Portal & MCP Hub" description: "Register the OAuth2 key managers applications can obtain access tokens from, using the API Portal Settings page." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/key-manager-integration/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/key-manager-integration.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/key-manager-integration/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/key-manager-integration.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/llm-instructions.md b/en/docs/api-portal/1.0.0/admin-settings/llm-instructions.md similarity index 95% rename from en/docs/next/api-portal/admin-settings/llm-instructions.md rename to en/docs/api-portal/1.0.0/admin-settings/llm-instructions.md index f9909b3ab..091958ff5 100644 --- a/en/docs/next/api-portal/admin-settings/llm-instructions.md +++ b/en/docs/api-portal/1.0.0/admin-settings/llm-instructions.md @@ -1,8 +1,8 @@ --- title: "Configure LLM instructions for the API Portal & MCP Hub" description: "Set the portal name and description that head the llms.txt file, and turn AI discoverability on or off for the whole portal." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/llm-instructions/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/llm-instructions.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/llm-instructions/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/llm-instructions.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/manage-api-workflows.md b/en/docs/api-portal/1.0.0/admin-settings/manage-api-workflows.md similarity index 96% rename from en/docs/next/api-portal/admin-settings/manage-api-workflows.md rename to en/docs/api-portal/1.0.0/admin-settings/manage-api-workflows.md index 884c83cc8..416464409 100644 --- a/en/docs/next/api-portal/admin-settings/manage-api-workflows.md +++ b/en/docs/api-portal/1.0.0/admin-settings/manage-api-workflows.md @@ -1,8 +1,8 @@ --- title: "Manage API workflows in the API Portal & MCP Hub" description: "Author an API workflow as an Arazzo spec or Markdown, generate its agent prompt, and publish it to a view." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/manage-api-workflows/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/manage-api-workflows.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-api-workflows/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-api-workflows.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/manage-apis.md b/en/docs/api-portal/1.0.0/admin-settings/manage-apis.md similarity index 95% rename from en/docs/next/api-portal/admin-settings/manage-apis.md rename to en/docs/api-portal/1.0.0/admin-settings/manage-apis.md index 71d956583..026bb9747 100644 --- a/en/docs/next/api-portal/admin-settings/manage-apis.md +++ b/en/docs/api-portal/1.0.0/admin-settings/manage-apis.md @@ -1,8 +1,8 @@ --- title: "Manage APIs in the API Portal & MCP Hub" description: "Add, edit, publish, deprecate, and delete the REST, WebSocket, GraphQL, WebSub, and SOAP APIs visible in the API Portal & MCP Hub." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/manage-apis/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/manage-apis.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-apis/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-apis.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/manage-labels.md b/en/docs/api-portal/1.0.0/admin-settings/manage-labels.md similarity index 88% rename from en/docs/next/api-portal/admin-settings/manage-labels.md rename to en/docs/api-portal/1.0.0/admin-settings/manage-labels.md index d4a7ebb80..8dbe57f9e 100644 --- a/en/docs/next/api-portal/admin-settings/manage-labels.md +++ b/en/docs/api-portal/1.0.0/admin-settings/manage-labels.md @@ -1,8 +1,8 @@ --- title: "Manage labels in the API Portal & MCP Hub" description: "Create, edit, and delete labels used to group APIs so that views can control which APIs are visible to consumers." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/manage-labels/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/manage-labels.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-labels/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-labels.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/manage-mcp-servers.md b/en/docs/api-portal/1.0.0/admin-settings/manage-mcp-servers.md similarity index 93% rename from en/docs/next/api-portal/admin-settings/manage-mcp-servers.md rename to en/docs/api-portal/1.0.0/admin-settings/manage-mcp-servers.md index a99855a47..4775c04cd 100644 --- a/en/docs/next/api-portal/admin-settings/manage-mcp-servers.md +++ b/en/docs/api-portal/1.0.0/admin-settings/manage-mcp-servers.md @@ -1,8 +1,8 @@ --- title: "Manage MCP servers in the API Portal & MCP Hub" description: "Add, edit, publish, deprecate, and delete the MCP servers exposed in the API Portal & MCP Hub." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/manage-mcp-servers/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/manage-mcp-servers.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-mcp-servers/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-mcp-servers.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/manage-views.md b/en/docs/api-portal/1.0.0/admin-settings/manage-views.md similarity index 90% rename from en/docs/next/api-portal/admin-settings/manage-views.md rename to en/docs/api-portal/1.0.0/admin-settings/manage-views.md index ce8a79700..6312e94e6 100644 --- a/en/docs/next/api-portal/admin-settings/manage-views.md +++ b/en/docs/api-portal/1.0.0/admin-settings/manage-views.md @@ -1,8 +1,8 @@ --- title: "Manage views in the API Portal & MCP Hub" description: "Create, edit, and delete views to scope which labelled APIs are visible to different audiences in the API Portal & MCP Hub." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/manage-views/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/manage-views.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-views/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-views.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/organization-settings.md b/en/docs/api-portal/1.0.0/admin-settings/organization-settings.md similarity index 92% rename from en/docs/next/api-portal/admin-settings/organization-settings.md rename to en/docs/api-portal/1.0.0/admin-settings/organization-settings.md index 92272015a..cd0535621 100644 --- a/en/docs/next/api-portal/admin-settings/organization-settings.md +++ b/en/docs/api-portal/1.0.0/admin-settings/organization-settings.md @@ -1,8 +1,8 @@ --- title: "Configure organization settings in the API Portal & MCP Hub" description: "Edit the current organization's display name, business owner contact, and identity provider reference from the API Portal Settings page." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/organization-settings/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/organization-settings.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/organization-settings/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/organization-settings.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/subscription-plans.md b/en/docs/api-portal/1.0.0/admin-settings/subscription-plans.md similarity index 93% rename from en/docs/next/api-portal/admin-settings/subscription-plans.md rename to en/docs/api-portal/1.0.0/admin-settings/subscription-plans.md index 65e806d31..3ace4d1ef 100644 --- a/en/docs/next/api-portal/admin-settings/subscription-plans.md +++ b/en/docs/api-portal/1.0.0/admin-settings/subscription-plans.md @@ -1,8 +1,8 @@ --- title: "Configure subscription plans in the API Portal & MCP Hub" description: "Define the rate and quota tiers that applications can subscribe to, and attach them to your published APIs." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/subscription-plans/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/subscription-plans.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/subscription-plans/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/subscription-plans.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/theming.md b/en/docs/api-portal/1.0.0/admin-settings/theming.md similarity index 98% rename from en/docs/next/api-portal/admin-settings/theming.md rename to en/docs/api-portal/1.0.0/admin-settings/theming.md index 03ecbe3e8..ff84c1171 100644 --- a/en/docs/next/api-portal/admin-settings/theming.md +++ b/en/docs/api-portal/1.0.0/admin-settings/theming.md @@ -1,8 +1,8 @@ --- title: "Theming the API Portal & MCP Hub" description: "Build a custom theme for a view—override styles, layouts, partials, and pages, re-color the portal from a few seed variables, and package it for upload." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/theming/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/theming.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/theming/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/theming.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/admin-settings/webhook-integration.md b/en/docs/api-portal/1.0.0/admin-settings/webhook-integration.md similarity index 96% rename from en/docs/next/api-portal/admin-settings/webhook-integration.md rename to en/docs/api-portal/1.0.0/admin-settings/webhook-integration.md index 1a8d94aaa..9946dbc11 100644 --- a/en/docs/next/api-portal/admin-settings/webhook-integration.md +++ b/en/docs/api-portal/1.0.0/admin-settings/webhook-integration.md @@ -1,8 +1,8 @@ --- title: "Configure webhooks in the API Portal & MCP Hub" description: "Register endpoints to receive signed, real-time notifications when applications, API keys, or subscriptions change." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/webhook-integration/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/admin-settings/webhook-integration.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/webhook-integration/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/webhook-integration.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/ai-agent-discovery.md b/en/docs/api-portal/1.0.0/ai-agent-discovery.md similarity index 98% rename from en/docs/next/api-portal/ai-agent-discovery.md rename to en/docs/api-portal/1.0.0/ai-agent-discovery.md index d86a7173a..6d5d97109 100644 --- a/en/docs/next/api-portal/ai-agent-discovery.md +++ b/en/docs/api-portal/1.0.0/ai-agent-discovery.md @@ -1,8 +1,8 @@ --- title: "AI agent API discovery" description: "Expose published APIs, MCP servers, and workflows through llms.txt and machine-readable Markdown endpoints so AI agents can discover and invoke them." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/ai-agent-discovery/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/ai-agent-discovery.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/ai-agent-discovery/ +md_url: https://wso2.com/api-platform/docs/api-portal/ai-agent-discovery.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/api-workflows.md b/en/docs/api-portal/1.0.0/api-workflows.md similarity index 97% rename from en/docs/next/api-portal/api-workflows.md rename to en/docs/api-portal/1.0.0/api-workflows.md index f3ed7ca02..01120762e 100644 --- a/en/docs/next/api-portal/api-workflows.md +++ b/en/docs/api-portal/1.0.0/api-workflows.md @@ -1,8 +1,8 @@ --- title: "API workflows in the API Portal & MCP Hub" description: "Find a published API workflow, read its steps in the portal, and hand it to an AI agent through the generated agent prompt or the Arazzo specification." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/api-workflows/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/api-workflows.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/api-workflows/ +md_url: https://wso2.com/api-platform/docs/api-portal/api-workflows.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/concepts.md b/en/docs/api-portal/1.0.0/concepts.md similarity index 98% rename from en/docs/next/api-portal/concepts.md rename to en/docs/api-portal/1.0.0/concepts.md index 858e61537..4af670bf3 100644 --- a/en/docs/next/api-portal/concepts.md +++ b/en/docs/api-portal/1.0.0/concepts.md @@ -1,8 +1,8 @@ --- title: "API Portal concepts" description: "The key building blocks of the API Portal & MCP Hub—organization, views, labels, APIs, MCP servers, API workflows, subscription plans, applications, subscriptions, API keys, and more—and how they relate to each other." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/concepts/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/concepts.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/concepts/ +md_url: https://wso2.com/api-platform/docs/api-portal/concepts.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/consume-an-api/api-key.md b/en/docs/api-portal/1.0.0/consume-an-api/api-key.md similarity index 95% rename from en/docs/next/api-portal/consume-an-api/api-key.md rename to en/docs/api-portal/1.0.0/consume-an-api/api-key.md index 15a709bb4..c0bf85df1 100644 --- a/en/docs/next/api-portal/consume-an-api/api-key.md +++ b/en/docs/api-portal/1.0.0/consume-an-api/api-key.md @@ -1,8 +1,8 @@ --- title: "Consume an API secured with an API key" description: "Send a generated API key in the header an API's specification declares, and combine it with a subscription token when the API requires one." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/api-key/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/api-key.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/api-key/ +md_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/api-key.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/consume-an-api/manage-api-keys.md b/en/docs/api-portal/1.0.0/consume-an-api/manage-api-keys.md similarity index 96% rename from en/docs/next/api-portal/consume-an-api/manage-api-keys.md rename to en/docs/api-portal/1.0.0/consume-an-api/manage-api-keys.md index c173ade6e..303adc74f 100644 --- a/en/docs/next/api-portal/consume-an-api/manage-api-keys.md +++ b/en/docs/api-portal/1.0.0/consume-an-api/manage-api-keys.md @@ -1,8 +1,8 @@ --- title: "Manage API keys" description: "Generate, rotate, revoke, and associate API keys for a subscribed API in the API Portal & MCP Hub." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/manage-api-keys/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/manage-api-keys.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/manage-api-keys/ +md_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/manage-api-keys.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/consume-an-api/manage-applications.md b/en/docs/api-portal/1.0.0/consume-an-api/manage-applications.md similarity index 96% rename from en/docs/next/api-portal/consume-an-api/manage-applications.md rename to en/docs/api-portal/1.0.0/consume-an-api/manage-applications.md index bdc5caa51..c7d4d23ec 100644 --- a/en/docs/next/api-portal/consume-an-api/manage-applications.md +++ b/en/docs/api-portal/1.0.0/consume-an-api/manage-applications.md @@ -1,8 +1,8 @@ --- title: "Manage applications in the API Portal & MCP Hub" description: "Create an application to hold OAuth2 client IDs, edit its name and description, associate API keys for analytics, and delete it." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/manage-applications/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/manage-applications.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/manage-applications/ +md_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/manage-applications.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/consume-an-api/manage-subscriptions.md b/en/docs/api-portal/1.0.0/consume-an-api/manage-subscriptions.md similarity index 95% rename from en/docs/next/api-portal/consume-an-api/manage-subscriptions.md rename to en/docs/api-portal/1.0.0/consume-an-api/manage-subscriptions.md index 2c0dd2897..005d821a3 100644 --- a/en/docs/next/api-portal/consume-an-api/manage-subscriptions.md +++ b/en/docs/api-portal/1.0.0/consume-an-api/manage-subscriptions.md @@ -1,8 +1,8 @@ --- title: "Manage subscriptions in the API Portal & MCP Hub" description: "Subscribe to an API under a plan, use the subscription token it issues, and switch, suspend, or cancel the subscription later." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/manage-subscriptions/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/manage-subscriptions.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/manage-subscriptions/ +md_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/manage-subscriptions.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/consume-an-api/oauth2.md b/en/docs/api-portal/1.0.0/consume-an-api/oauth2.md similarity index 96% rename from en/docs/next/api-portal/consume-an-api/oauth2.md rename to en/docs/api-portal/1.0.0/consume-an-api/oauth2.md index 8baac653e..105c4bcb6 100644 --- a/en/docs/next/api-portal/consume-an-api/oauth2.md +++ b/en/docs/api-portal/1.0.0/consume-an-api/oauth2.md @@ -1,8 +1,8 @@ --- title: "Consume an API secured with OAuth2" description: "Link a key manager client ID to your application, generate an access token from the portal or with curl, and call an OAuth2-secured API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/oauth2/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/oauth2.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/oauth2/ +md_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/oauth2.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/consume-an-api/overview.md b/en/docs/api-portal/1.0.0/consume-an-api/overview.md similarity index 96% rename from en/docs/next/api-portal/consume-an-api/overview.md rename to en/docs/api-portal/1.0.0/consume-an-api/overview.md index c7311bdd8..f3190eb22 100644 --- a/en/docs/next/api-portal/consume-an-api/overview.md +++ b/en/docs/api-portal/1.0.0/consume-an-api/overview.md @@ -1,8 +1,8 @@ --- title: "Consume an API: which credentials you need" description: "Work out which of the three credentials an API expects—a subscription token, an API key, or an OAuth2 access token—and how to get each one." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/overview/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/consume-an-api/overview.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/overview.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/discover-apis/browse-apis.md b/en/docs/api-portal/1.0.0/discover-apis/browse-apis.md similarity index 97% rename from en/docs/next/api-portal/discover-apis/browse-apis.md rename to en/docs/api-portal/1.0.0/discover-apis/browse-apis.md index 3d94052fd..b5607d77c 100644 --- a/en/docs/next/api-portal/discover-apis/browse-apis.md +++ b/en/docs/api-portal/1.0.0/discover-apis/browse-apis.md @@ -1,8 +1,8 @@ --- title: "Browse APIs in the API Portal & MCP Hub" description: "Browse and search published APIs in the API Portal & MCP Hub by name, version, type, description, tags, or documentation content." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/browse-apis/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/browse-apis.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/discover-apis/browse-apis/ +md_url: https://wso2.com/api-platform/docs/api-portal/discover-apis/browse-apis.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/discover-apis/overview.md b/en/docs/api-portal/1.0.0/discover-apis/overview.md similarity index 95% rename from en/docs/next/api-portal/discover-apis/overview.md rename to en/docs/api-portal/1.0.0/discover-apis/overview.md index 808ce451a..744917309 100644 --- a/en/docs/next/api-portal/discover-apis/overview.md +++ b/en/docs/api-portal/1.0.0/discover-apis/overview.md @@ -1,8 +1,8 @@ --- title: "APIs in the API Portal & MCP Hub" description: "What an API is in the API Portal & MCP Hub, the API types it publishes, the two routes APIs take into the catalog, and how they differ from MCP servers." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/overview/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/discover-apis/overview.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/discover-apis/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/discover-apis/overview.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/getting-started.md b/en/docs/api-portal/1.0.0/getting-started.md similarity index 98% rename from en/docs/next/api-portal/getting-started.md rename to en/docs/api-portal/1.0.0/getting-started.md index b22220ce9..1646b4554 100644 --- a/en/docs/next/api-portal/getting-started.md +++ b/en/docs/api-portal/1.0.0/getting-started.md @@ -1,8 +1,8 @@ --- title: "Get started with API Portal & MCP Hub" description: "Run the API Portal & MCP Hub locally with Docker Compose, sign in, and publish your first API to the catalog." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/getting-started/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/getting-started.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/getting-started/ +md_url: https://wso2.com/api-platform/docs/api-portal/getting-started.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/mcp-registry.md b/en/docs/api-portal/1.0.0/mcp-registry.md similarity index 98% rename from en/docs/next/api-portal/mcp-registry.md rename to en/docs/api-portal/1.0.0/mcp-registry.md index 50b05fe2c..ed69d83b9 100644 --- a/en/docs/next/api-portal/mcp-registry.md +++ b/en/docs/api-portal/1.0.0/mcp-registry.md @@ -1,8 +1,8 @@ --- title: "MCP Registry API" description: "Discover and publish MCP servers through the API Portal & MCP Hub's implementation of the Model Context Protocol registry specification." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-registry/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-registry.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/mcp-registry/ +md_url: https://wso2.com/api-platform/docs/api-portal/mcp-registry.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md b/en/docs/api-portal/1.0.0/mcp-servers/browse-mcp-servers.md similarity index 96% rename from en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md rename to en/docs/api-portal/1.0.0/mcp-servers/browse-mcp-servers.md index 9b657cb0e..b82067286 100644 --- a/en/docs/next/api-portal/mcp-servers/browse-mcp-servers.md +++ b/en/docs/api-portal/1.0.0/mcp-servers/browse-mcp-servers.md @@ -1,8 +1,8 @@ --- title: "Browse MCP servers in the API Portal & MCP Hub" description: "Browse and search the MCP catalog, read a server's tools, resources, and prompts, and invoke them from the MCP Playground." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/browse-mcp-servers/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/browse-mcp-servers.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/mcp-servers/browse-mcp-servers/ +md_url: https://wso2.com/api-platform/docs/api-portal/mcp-servers/browse-mcp-servers.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md b/en/docs/api-portal/1.0.0/mcp-servers/connect-to-an-mcp-server.md similarity index 95% rename from en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md rename to en/docs/api-portal/1.0.0/mcp-servers/connect-to-an-mcp-server.md index ee77f90ae..02ce7caee 100644 --- a/en/docs/next/api-portal/mcp-servers/connect-to-an-mcp-server.md +++ b/en/docs/api-portal/1.0.0/mcp-servers/connect-to-an-mcp-server.md @@ -1,8 +1,8 @@ --- title: "Connect to an MCP server" description: "Subscribe to an MCP server, obtain a bearer token, and paste the portal's configuration snippet into your MCP client." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/connect-to-an-mcp-server/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/connect-to-an-mcp-server.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/mcp-servers/connect-to-an-mcp-server/ +md_url: https://wso2.com/api-platform/docs/api-portal/mcp-servers/connect-to-an-mcp-server.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/mcp-servers/overview.md b/en/docs/api-portal/1.0.0/mcp-servers/overview.md similarity index 95% rename from en/docs/next/api-portal/mcp-servers/overview.md rename to en/docs/api-portal/1.0.0/mcp-servers/overview.md index 0ca7cce30..943f87b2a 100644 --- a/en/docs/next/api-portal/mcp-servers/overview.md +++ b/en/docs/api-portal/1.0.0/mcp-servers/overview.md @@ -1,8 +1,8 @@ --- title: "MCP servers in the API Portal & MCP Hub" description: "What an MCP server is in the API Portal & MCP Hub, the two routes servers take into the catalog, and how they differ from APIs." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/overview/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/mcp-servers/overview.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/mcp-servers/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/mcp-servers/overview.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/overview.md b/en/docs/api-portal/1.0.0/overview.md similarity index 98% rename from en/docs/next/api-portal/overview.md rename to en/docs/api-portal/1.0.0/overview.md index ff0e79689..7abc100b6 100644 --- a/en/docs/next/api-portal/overview.md +++ b/en/docs/api-portal/1.0.0/overview.md @@ -1,8 +1,8 @@ --- title: "API Portal & MCP Hub overview" description: "The API Portal & MCP Hub is a portal where API publishers expose APIs and MCP servers, and developers discover, subscribe, and consume them." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/overview/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/overview.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/overview.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/references/configurations.md b/en/docs/api-portal/1.0.0/references/configurations.md similarity index 98% rename from en/docs/next/api-portal/references/configurations.md rename to en/docs/api-portal/1.0.0/references/configurations.md index 5b912a989..c071acd14 100644 --- a/en/docs/next/api-portal/references/configurations.md +++ b/en/docs/api-portal/1.0.0/references/configurations.md @@ -1,8 +1,8 @@ --- title: "API Portal configuration reference" description: "How the API Portal & MCP Hub loads config.toml, injects environment values through interpolation tokens, and the full reference of every supported configuration key." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/references/configurations/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/references/configurations.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/references/configurations/ +md_url: https://wso2.com/api-platform/docs/api-portal/references/configurations.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/references/get-a-bearer-token-via-curl.md b/en/docs/api-portal/1.0.0/references/get-a-bearer-token-via-curl.md similarity index 96% rename from en/docs/next/api-portal/references/get-a-bearer-token-via-curl.md rename to en/docs/api-portal/1.0.0/references/get-a-bearer-token-via-curl.md index cd7bb444f..a09ed3231 100644 --- a/en/docs/next/api-portal/references/get-a-bearer-token-via-curl.md +++ b/en/docs/api-portal/1.0.0/references/get-a-bearer-token-via-curl.md @@ -1,8 +1,8 @@ --- title: "Get a Bearer token via curl (IdP mode)" description: "Obtain a Bearer token for the API Portal REST API from the terminal, without a browser, when running in external IdP mode." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/references/get-a-bearer-token-via-curl/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/references/get-a-bearer-token-via-curl.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/references/get-a-bearer-token-via-curl/ +md_url: https://wso2.com/api-platform/docs/api-portal/references/get-a-bearer-token-via-curl.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/references/webhook-event-catalog.md b/en/docs/api-portal/1.0.0/references/webhook-event-catalog.md similarity index 98% rename from en/docs/next/api-portal/references/webhook-event-catalog.md rename to en/docs/api-portal/1.0.0/references/webhook-event-catalog.md index 51cb51801..772620a1c 100644 --- a/en/docs/next/api-portal/references/webhook-event-catalog.md +++ b/en/docs/api-portal/1.0.0/references/webhook-event-catalog.md @@ -1,8 +1,8 @@ --- title: "Webhook event catalog" description: "Every webhook event the API Portal & MCP Hub publishes, with its payload fields, the delivery envelope, headers, signature verification, and field encryption." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/references/webhook-event-catalog/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/references/webhook-event-catalog.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/references/webhook-event-catalog/ +md_url: https://wso2.com/api-platform/docs/api-portal/references/webhook-event-catalog.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/api-content.md b/en/docs/api-portal/1.0.0/rest-api/api-content.md similarity index 98% rename from en/docs/next/api-portal/rest-api/api-content.md rename to en/docs/api-portal/1.0.0/rest-api/api-content.md index e60d28be4..6bd072353 100644 --- a/en/docs/next/api-portal/rest-api/api-content.md +++ b/en/docs/api-portal/1.0.0/rest-api/api-content.md @@ -1,8 +1,8 @@ --- title: "API Content" description: "Upload, replace, get, and delete an API's landing page content and documentation via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/api-content/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/api-content.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/api-content/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/api-content.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/api-keys.md b/en/docs/api-portal/1.0.0/rest-api/api-keys.md similarity index 99% rename from en/docs/next/api-portal/rest-api/api-keys.md rename to en/docs/api-portal/1.0.0/rest-api/api-keys.md index b124239bb..55d6c1f19 100644 --- a/en/docs/next/api-portal/rest-api/api-keys.md +++ b/en/docs/api-portal/1.0.0/rest-api/api-keys.md @@ -1,8 +1,8 @@ --- title: "API Keys" description: "Generate, list, regenerate, revoke, and associate API keys via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/api-keys/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/api-keys.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/api-keys/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/api-keys.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/api-workflows.md b/en/docs/api-portal/1.0.0/rest-api/api-workflows.md similarity index 98% rename from en/docs/next/api-portal/rest-api/api-workflows.md rename to en/docs/api-portal/1.0.0/rest-api/api-workflows.md index 3d6a0c4eb..c8a44b055 100644 --- a/en/docs/next/api-portal/rest-api/api-workflows.md +++ b/en/docs/api-portal/1.0.0/rest-api/api-workflows.md @@ -1,8 +1,8 @@ --- title: "API Workflows" description: "Create, list, get, update, delete, and generate an agent prompt for API workflows via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/api-workflows/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/api-workflows.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/api-workflows/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/api-workflows.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/apis.md b/en/docs/api-portal/1.0.0/rest-api/apis.md similarity index 99% rename from en/docs/next/api-portal/rest-api/apis.md rename to en/docs/api-portal/1.0.0/rest-api/apis.md index c43136dce..a29d43101 100644 --- a/en/docs/next/api-portal/rest-api/apis.md +++ b/en/docs/api-portal/1.0.0/rest-api/apis.md @@ -1,8 +1,8 @@ --- title: "APIs" description: "Create, list, get, update, and delete API metadata via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/apis/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/apis.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/apis/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/apis.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/application-keys.md b/en/docs/api-portal/1.0.0/rest-api/application-keys.md similarity index 98% rename from en/docs/next/api-portal/rest-api/application-keys.md rename to en/docs/api-portal/1.0.0/rest-api/application-keys.md index 839077b7c..9ff84b235 100644 --- a/en/docs/next/api-portal/rest-api/application-keys.md +++ b/en/docs/api-portal/1.0.0/rest-api/application-keys.md @@ -1,8 +1,8 @@ --- title: "Application Keys" description: "Map an OAuth client ID, generate an access token, and remove a client ID mapping via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/application-keys/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/application-keys.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/application-keys/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/application-keys.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/applications.md b/en/docs/api-portal/1.0.0/rest-api/applications.md similarity index 98% rename from en/docs/next/api-portal/rest-api/applications.md rename to en/docs/api-portal/1.0.0/rest-api/applications.md index f979cfc6c..05696d1b2 100644 --- a/en/docs/next/api-portal/rest-api/applications.md +++ b/en/docs/api-portal/1.0.0/rest-api/applications.md @@ -1,8 +1,8 @@ --- title: "Applications" description: "List, create, get, update, and delete applications via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/applications/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/applications.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/applications/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/applications.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/authentication.md b/en/docs/api-portal/1.0.0/rest-api/authentication.md similarity index 96% rename from en/docs/next/api-portal/rest-api/authentication.md rename to en/docs/api-portal/1.0.0/rest-api/authentication.md index 906486918..97308dc38 100644 --- a/en/docs/next/api-portal/rest-api/authentication.md +++ b/en/docs/api-portal/1.0.0/rest-api/authentication.md @@ -1,8 +1,8 @@ --- title: "Authenticate to the API Portal REST API" description: "OAuth2/OIDC scopes and API key authentication for the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/authentication/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/authentication.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/authentication/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/authentication.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/key-managers.md b/en/docs/api-portal/1.0.0/rest-api/key-managers.md similarity index 98% rename from en/docs/next/api-portal/rest-api/key-managers.md rename to en/docs/api-portal/1.0.0/rest-api/key-managers.md index 98f782d2f..48540a4a7 100644 --- a/en/docs/next/api-portal/rest-api/key-managers.md +++ b/en/docs/api-portal/1.0.0/rest-api/key-managers.md @@ -1,8 +1,8 @@ --- title: "Key Managers" description: "Create, list, get, update, and delete key managers via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/key-managers/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/key-managers.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/key-managers/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/key-managers.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/labels.md b/en/docs/api-portal/1.0.0/rest-api/labels.md similarity index 98% rename from en/docs/next/api-portal/rest-api/labels.md rename to en/docs/api-portal/1.0.0/rest-api/labels.md index 33a5f9d2b..ca3c7572a 100644 --- a/en/docs/next/api-portal/rest-api/labels.md +++ b/en/docs/api-portal/1.0.0/rest-api/labels.md @@ -1,8 +1,8 @@ --- title: "Labels" description: "Create, list, update, and delete labels via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/labels/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/labels.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/labels/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/labels.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/mcp-server-content.md b/en/docs/api-portal/1.0.0/rest-api/mcp-server-content.md similarity index 98% rename from en/docs/next/api-portal/rest-api/mcp-server-content.md rename to en/docs/api-portal/1.0.0/rest-api/mcp-server-content.md index 750e3fa26..490683be2 100644 --- a/en/docs/next/api-portal/rest-api/mcp-server-content.md +++ b/en/docs/api-portal/1.0.0/rest-api/mcp-server-content.md @@ -1,8 +1,8 @@ --- title: "MCP Server Content" description: "Upload, replace, get, and delete an MCP server's content via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/mcp-server-content/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/mcp-server-content.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/mcp-server-content/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/mcp-server-content.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/mcp-server-keys.md b/en/docs/api-portal/1.0.0/rest-api/mcp-server-keys.md similarity index 99% rename from en/docs/next/api-portal/rest-api/mcp-server-keys.md rename to en/docs/api-portal/1.0.0/rest-api/mcp-server-keys.md index 3dec20d65..06df61945 100644 --- a/en/docs/next/api-portal/rest-api/mcp-server-keys.md +++ b/en/docs/api-portal/1.0.0/rest-api/mcp-server-keys.md @@ -1,8 +1,8 @@ --- title: "MCP Server Keys" description: "Generate, list, regenerate, revoke, and associate MCP server API keys via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/mcp-server-keys/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/mcp-server-keys.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/mcp-server-keys/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/mcp-server-keys.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/mcp-servers.md b/en/docs/api-portal/1.0.0/rest-api/mcp-servers.md similarity index 99% rename from en/docs/next/api-portal/rest-api/mcp-servers.md rename to en/docs/api-portal/1.0.0/rest-api/mcp-servers.md index b1899fe65..734492e98 100644 --- a/en/docs/next/api-portal/rest-api/mcp-servers.md +++ b/en/docs/api-portal/1.0.0/rest-api/mcp-servers.md @@ -1,8 +1,8 @@ --- title: "MCP Servers" description: "Create, list, get, update, and delete MCP server metadata via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/mcp-servers/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/mcp-servers.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/mcp-servers/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/mcp-servers.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/organization-content.md b/en/docs/api-portal/1.0.0/rest-api/organization-content.md similarity index 97% rename from en/docs/next/api-portal/rest-api/organization-content.md rename to en/docs/api-portal/1.0.0/rest-api/organization-content.md index a07235e6d..634da5159 100644 --- a/en/docs/next/api-portal/rest-api/organization-content.md +++ b/en/docs/api-portal/1.0.0/rest-api/organization-content.md @@ -1,8 +1,8 @@ --- title: "Organization Content" description: "Get, apply, reset, and download an organization's theme assets via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/organization-content/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/organization-content.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/organization-content/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/organization-content.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/organizations.md b/en/docs/api-portal/1.0.0/rest-api/organizations.md similarity index 99% rename from en/docs/next/api-portal/rest-api/organizations.md rename to en/docs/api-portal/1.0.0/rest-api/organizations.md index 747087add..e6772163f 100644 --- a/en/docs/next/api-portal/rest-api/organizations.md +++ b/en/docs/api-portal/1.0.0/rest-api/organizations.md @@ -1,8 +1,8 @@ --- title: "Organizations" description: "Create, list, update, and delete organizations via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/organizations/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/organizations.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/organizations/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/organizations.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/overview.md b/en/docs/api-portal/1.0.0/rest-api/overview.md similarity index 98% rename from en/docs/next/api-portal/rest-api/overview.md rename to en/docs/api-portal/1.0.0/rest-api/overview.md index c9bce6fd7..9f8656674 100644 --- a/en/docs/next/api-portal/rest-api/overview.md +++ b/en/docs/api-portal/1.0.0/rest-api/overview.md @@ -1,8 +1,8 @@ --- title: "API Portal Management API" description: "Overview of the API Portal Management API for managing organizations, APIs, MCP servers, applications, subscriptions, and API keys." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/overview/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/overview.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/overview.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/schemas.md b/en/docs/api-portal/1.0.0/rest-api/schemas.md similarity index 99% rename from en/docs/next/api-portal/rest-api/schemas.md rename to en/docs/api-portal/1.0.0/rest-api/schemas.md index 691c5d0b6..3d714f0ff 100644 --- a/en/docs/next/api-portal/rest-api/schemas.md +++ b/en/docs/api-portal/1.0.0/rest-api/schemas.md @@ -1,8 +1,8 @@ --- title: "Schemas" description: "Data model schemas referenced by the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/schemas/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/schemas.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/schemas/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/schemas.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/subscription-plans.md b/en/docs/api-portal/1.0.0/rest-api/subscription-plans.md similarity index 99% rename from en/docs/next/api-portal/rest-api/subscription-plans.md rename to en/docs/api-portal/1.0.0/rest-api/subscription-plans.md index 8e1ba6e83..a9d33debc 100644 --- a/en/docs/next/api-portal/rest-api/subscription-plans.md +++ b/en/docs/api-portal/1.0.0/rest-api/subscription-plans.md @@ -1,8 +1,8 @@ --- title: "Subscription Plans" description: "List, create, upsert, get, and delete subscription plans via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/subscription-plans/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/subscription-plans.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/subscription-plans/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/subscription-plans.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/subscriptions.md b/en/docs/api-portal/1.0.0/rest-api/subscriptions.md similarity index 99% rename from en/docs/next/api-portal/rest-api/subscriptions.md rename to en/docs/api-portal/1.0.0/rest-api/subscriptions.md index 45cf0b600..74354b486 100644 --- a/en/docs/next/api-portal/rest-api/subscriptions.md +++ b/en/docs/api-portal/1.0.0/rest-api/subscriptions.md @@ -1,8 +1,8 @@ --- title: "Subscriptions" description: "Create, list, get, update, delete, change plan, and regenerate the token for a subscription via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/subscriptions/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/subscriptions.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/subscriptions/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/subscriptions.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/views.md b/en/docs/api-portal/1.0.0/rest-api/views.md similarity index 98% rename from en/docs/next/api-portal/rest-api/views.md rename to en/docs/api-portal/1.0.0/rest-api/views.md index 82eaabb23..a7e350ca1 100644 --- a/en/docs/next/api-portal/rest-api/views.md +++ b/en/docs/api-portal/1.0.0/rest-api/views.md @@ -1,8 +1,8 @@ --- title: "Views" description: "Create, list, update, and delete views via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/views/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/views.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/views/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/views.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/webhook-events.md b/en/docs/api-portal/1.0.0/rest-api/webhook-events.md similarity index 97% rename from en/docs/next/api-portal/rest-api/webhook-events.md rename to en/docs/api-portal/1.0.0/rest-api/webhook-events.md index 104bd7979..5f391c693 100644 --- a/en/docs/next/api-portal/rest-api/webhook-events.md +++ b/en/docs/api-portal/1.0.0/rest-api/webhook-events.md @@ -1,8 +1,8 @@ --- title: "Webhook Events" description: "List and get webhook events via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/webhook-events/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/webhook-events.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/webhook-events/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/webhook-events.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/rest-api/webhook-subscribers.md b/en/docs/api-portal/1.0.0/rest-api/webhook-subscribers.md similarity index 99% rename from en/docs/next/api-portal/rest-api/webhook-subscribers.md rename to en/docs/api-portal/1.0.0/rest-api/webhook-subscribers.md index 242510d23..0de73d420 100644 --- a/en/docs/next/api-portal/rest-api/webhook-subscribers.md +++ b/en/docs/api-portal/1.0.0/rest-api/webhook-subscribers.md @@ -1,8 +1,8 @@ --- title: "Webhook Subscribers" description: "Create, list, get, update, delete, and view recent deliveries for webhook subscribers via the API Portal REST API." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/webhook-subscribers/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/rest-api/webhook-subscribers.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/webhook-subscribers/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/webhook-subscribers.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/setting-up/artifact-types.md b/en/docs/api-portal/1.0.0/setting-up/artifact-types.md similarity index 92% rename from en/docs/next/api-portal/setting-up/artifact-types.md rename to en/docs/api-portal/1.0.0/setting-up/artifact-types.md index b2325660e..60d3161f9 100644 --- a/en/docs/next/api-portal/setting-up/artifact-types.md +++ b/en/docs/api-portal/1.0.0/setting-up/artifact-types.md @@ -1,8 +1,8 @@ --- title: "Artifact types" description: "Choose which artifact types—APIs, Model Context Protocol (MCP) servers, and API workflows—the API Portal & MCP Hub serves, via the api_portal.artifacts table in config.toml." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/artifact-types/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/artifact-types.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/artifact-types/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/artifact-types.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/setting-up/authentication/asgardeo-as-idp.md b/en/docs/api-portal/1.0.0/setting-up/authentication/asgardeo-as-idp.md similarity index 98% rename from en/docs/next/api-portal/setting-up/authentication/asgardeo-as-idp.md rename to en/docs/api-portal/1.0.0/setting-up/authentication/asgardeo-as-idp.md index f11605dde..ace46e5bd 100644 --- a/en/docs/next/api-portal/setting-up/authentication/asgardeo-as-idp.md +++ b/en/docs/api-portal/1.0.0/setting-up/authentication/asgardeo-as-idp.md @@ -1,8 +1,8 @@ --- title: "Set up Asgardeo as your identity provider" description: "Configure WSO2 Asgardeo as the OIDC identity provider for a production API Portal deployment, from application registration to config.toml." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/authentication/asgardeo-as-idp/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/authentication/asgardeo-as-idp.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/authentication/asgardeo-as-idp/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/authentication/asgardeo-as-idp.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/setting-up/authentication/connect-an-identity-provider.md b/en/docs/api-portal/1.0.0/setting-up/authentication/connect-an-identity-provider.md similarity index 98% rename from en/docs/next/api-portal/setting-up/authentication/connect-an-identity-provider.md rename to en/docs/api-portal/1.0.0/setting-up/authentication/connect-an-identity-provider.md index b870fdda7..bd9aa96c2 100644 --- a/en/docs/next/api-portal/setting-up/authentication/connect-an-identity-provider.md +++ b/en/docs/api-portal/1.0.0/setting-up/authentication/connect-an-identity-provider.md @@ -1,8 +1,8 @@ --- title: "Connect an identity provider to the API Portal" description: "Configure the API Portal & MCP Hub to delegate login to any OIDC identity provider: client registration, claim mappings, role or scope authorization, and the config.toml tables involved." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/authentication/connect-an-identity-provider/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/authentication/connect-an-identity-provider.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/authentication/connect-an-identity-provider/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/authentication/connect-an-identity-provider.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/setting-up/authentication/overview.md b/en/docs/api-portal/1.0.0/setting-up/authentication/overview.md similarity index 96% rename from en/docs/next/api-portal/setting-up/authentication/overview.md rename to en/docs/api-portal/1.0.0/setting-up/authentication/overview.md index c1c4011f8..91ff03a7f 100644 --- a/en/docs/next/api-portal/setting-up/authentication/overview.md +++ b/en/docs/api-portal/1.0.0/setting-up/authentication/overview.md @@ -1,8 +1,8 @@ --- title: "Authentication in the API Portal & MCP Hub" description: "Understand the two ways users sign in to the API Portal & MCP Hub: local authentication against the Platform API for development, and an OIDC identity provider for production." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/authentication/overview/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/authentication/overview.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/authentication/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/authentication/overview.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/setting-up/configuration.md b/en/docs/api-portal/1.0.0/setting-up/configuration.md similarity index 97% rename from en/docs/next/api-portal/setting-up/configuration.md rename to en/docs/api-portal/1.0.0/setting-up/configuration.md index 1fb33aeee..ecb3a0667 100644 --- a/en/docs/next/api-portal/setting-up/configuration.md +++ b/en/docs/api-portal/1.0.0/setting-up/configuration.md @@ -1,8 +1,8 @@ --- title: "API Portal configuration and environment interpolation" description: "How the API Portal & MCP Hub loads its config.toml, injects environment values and mounted files through interpolation tokens, and keeps sensitive values out of the config file." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/configuration/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/configuration.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/configuration/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/configuration.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/setting-up/database.md b/en/docs/api-portal/1.0.0/setting-up/database.md similarity index 97% rename from en/docs/next/api-portal/setting-up/database.md rename to en/docs/api-portal/1.0.0/setting-up/database.md index 7b42402ca..eeedcba76 100644 --- a/en/docs/next/api-portal/setting-up/database.md +++ b/en/docs/api-portal/1.0.0/setting-up/database.md @@ -1,8 +1,8 @@ --- title: "Set up the database" description: "Configure the API Portal & MCP Hub to run on SQLite, PostgreSQL, or Microsoft SQL Server: connection settings, schema, and Transport Layer Security (TLS)." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/database/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/database.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/database/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/database.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/setting-up/ports.md b/en/docs/api-portal/1.0.0/setting-up/ports.md similarity index 97% rename from en/docs/next/api-portal/setting-up/ports.md rename to en/docs/api-portal/1.0.0/setting-up/ports.md index f64286e9b..2fdec2ac1 100644 --- a/en/docs/next/api-portal/setting-up/ports.md +++ b/en/docs/api-portal/1.0.0/setting-up/ports.md @@ -1,8 +1,8 @@ --- title: "Change the ports the API Portal uses" description: "Move the API Portal & MCP Hub and the Platform API off their default ports, either through the APIP_AP_SERVER_PORT variable or by editing the Compose port mappings." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/ports/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/setting-up/ports.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/ports/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/ports.md tags: - cloud - api-portal diff --git a/en/docs/next/api-portal/tutorials/secured-api-end-to-end.md b/en/docs/api-portal/1.0.0/tutorials/secured-api-end-to-end.md similarity index 99% rename from en/docs/next/api-portal/tutorials/secured-api-end-to-end.md rename to en/docs/api-portal/1.0.0/tutorials/secured-api-end-to-end.md index eae3a3057..38bea8abc 100644 --- a/en/docs/next/api-portal/tutorials/secured-api-end-to-end.md +++ b/en/docs/api-portal/1.0.0/tutorials/secured-api-end-to-end.md @@ -1,8 +1,8 @@ --- title: "End to end: a secured API from gateway to portal" description: "Stand up the control plane, portal, and gateway, publish an API that requires both a subscription token and an API key, and invoke it with credentials issued in the API Portal." -canonical_url: https://wso2.com/api-platform/docs/cloud/api-portal/tutorials/secured-api-end-to-end/ -md_url: https://wso2.com/api-platform/docs/cloud/api-portal/tutorials/secured-api-end-to-end.md +canonical_url: https://wso2.com/api-platform/docs/api-portal/tutorials/secured-api-end-to-end/ +md_url: https://wso2.com/api-platform/docs/api-portal/tutorials/secured-api-end-to-end.md tags: - cloud - api-portal diff --git a/en/docs/api-portal/next/about-this-release.md b/en/docs/api-portal/next/about-this-release.md new file mode 100644 index 000000000..88fe1c151 --- /dev/null +++ b/en/docs/api-portal/next/about-this-release.md @@ -0,0 +1,164 @@ +--- +title: "About this release" +description: "What's included in API Portal & MCP Hub 1.0.0: the API and MCP catalog, API workflows, MCP registry, applications, authentication, theming, and the admin UI." +canonical_url: https://wso2.com/api-platform/docs/api-portal/about-this-release/ +md_url: https://wso2.com/api-platform/docs/api-portal/about-this-release.md +tags: + - cloud + - api-portal + - release-notes +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-04 +content_type: "release-notes" +--- + +# About this release + +The API Portal & MCP Hub is a web application that serves a catalog of APIs, Model Context Protocol (MCP) servers, and API workflows. Publishers register artifacts in it, and developers browse them, subscribe to plans, generate credentials, and call the APIs. It is a standalone product: it runs as its own distribution, keeps its own database, authenticates against your identity provider, and reaches gateways through signed webhook events rather than a fixed control-plane binding. + +**API Portal & MCP Hub 1.0.0** is the first **API Portal & MCP Hub release**. Every capability listed below is available for the first time, so there is no predecessor to upgrade from. + +For more information on the API Portal & MCP Hub, see the [overview](overview.md). + +## Downloads + +Download the standalone distribution from the WSO2 API Platform release page: + +```bash +curl -sLO https://github.com/wso2/api-platform/releases/download/api-portal%2Fv1.0.0/wso2apip-api-portal-1.0.0.zip && \ +unzip wso2apip-api-portal-1.0.0.zip +``` + +To install and run it, follow the [Getting started](getting-started.md) guide. + +## New features + +??? note "API and MCP server catalog" + + A single catalog serves APIs and MCP servers side by side, so the portal doubles as an MCP Hub with MCP servers as first-class entries rather than a secondary artifact type. + + - **Unified browsing and search**: Find APIs and MCP servers by name, description, label, or view. + - **Views**: Group artifacts into separate catalog surfaces, each with its own theme and audience. + - **Labels**: Tag and filter artifacts across views. + - **Per-artifact documentation**: Publish specifications, guides, and custom content next to each entry. + + **[Learn more](discover-apis/browse-apis.md)** + +??? note "API workflows" + + Multi-step, guided API workflows are a first-class catalog artifact alongside APIs and MCP servers, so a sequence of calls that achieves one outcome can be published as a single discoverable unit. + + - **Guided consumption**: Present an ordered sequence of API calls with the context a consumer needs at each step. + - **Catalog parity**: Workflows carry the same views, labels, and documentation as APIs. + - **Selective serving**: Include or exclude workflows per instance through `[api_portal.artifacts] enabled_types`. + + **[Learn more](api-workflows.md)** + +??? note "MCP Server Registry API" + + A programmatic registry lets agents and tooling enumerate the MCP servers a portal publishes, without scraping the catalog UI. + + - **Machine-readable discovery**: Query published MCP servers and their connection details over HTTP. + - **Agent-oriented**: Intended for MCP clients and agent frameworks that resolve servers at runtime. + + **[Learn more](mcp-registry.md)** + +??? note "AI agent discovery" + + The portal publishes `llms.txt` entry points at its root and per view, pairing with the MCP registry so an AI agent can discover what a portal offers programmatically. + + - **`llms.txt` entry points**: Machine-readable indexes of the APIs, MCP servers, and workflows a view serves. + - **Administrator-authored guidance**: Supply instructions that shape how large language models (LLMs) interpret the catalog. + + **[Learn more](ai-agent-discovery.md)** + +??? note "Applications, subscriptions, and API keys" + + Consumers self-serve the credentials they need, from creating an application through calling a secured API in the browser. + + - **Applications**: Create and manage applications that hold credentials. + - **Subscriptions**: Subscribe applications to artifacts under a chosen subscription plan. + - **API keys and subscription tokens**: Generate, view, and revoke both credential types. + - **Try It console**: Call an API from the catalog page, through a same-origin proxy that avoids per-backend cross-origin resource sharing (CORS) configuration. + + **[Learn more](consume-an-api/overview.md)** + +??? note "Local and OIDC authentication" + + The portal authenticates users in one of two modes, selected by `[api_portal.auth] mode`, so an evaluation needs no identity provider while production delegates login to one. + + - **Platform API-backed local authentication**: The built-in login form validates credentials against the Platform API control plane and receives a signed JSON Web Token (JWT). + - **OpenID Connect (OIDC) authentication**: Delegate login to any OIDC-compliant identity provider, with the portal acting as a confidential client. + - **Configurable claim mappings**: Name the claims that carry organization, role, and group information, including dot-separated paths into nested claims. + - **Role or scope authorization**: Expand a token's roles through a grant table, or read `dp:*` scopes the identity provider mints directly. + + **[Learn more](setting-up/authentication/overview.md)** + +??? note "Theming" + + Portal appearance is operator-controlled, from global styling down to individual API landing pages. + + - **Global theming**: Set colors, styling, and logos for the portal. + - **Per-view layouts**: Upload page layouts that apply to a single view. + - **Per-API styling**: Apply custom styling to an individual API's landing page. + + **[Learn more](admin-settings/theming.md)** + +??? note "Design mode" + + A file-based preview renders the whole portal from sample files on disk, opening no database connection and making no Platform API calls. + + - **No infrastructure**: Preview APIs, MCP servers, applications, and theming without standing up the full stack. + - **Content and theme authoring**: Iterate on layouts and catalog content directly from disk. + + **[Learn more](admin-settings/design-mode.md)** + +??? note "Webhook-based event integration" + + The portal emits signed events for credential and plan changes rather than holding gateway-specific logic. Most deployments subscribe the Platform API control plane, which propagates each change to the gateways the API is deployed to. + + - **Signed delivery**: Events for API key, application, and subscription plan changes carry a signature the subscriber verifies, and credential fields arrive encrypted. + - **Control plane integration**: Register the Platform API as a subscriber and it persists each credential and pushes it out to every gateway serving the API. A gateway or a handler of your own can subscribe directly instead. + - **Per-organization subscribers**: Register receivers through the Settings UI or the Management API rather than static configuration, so your own handler can subscribe alongside the control plane. + + **[Learn more](admin-settings/webhook-integration.md)** + +??? note "Admin UI and Management API" + + A dedicated administrative interface, backed by a documented REST API, manages everything an operator configures at runtime. + + - **Organization and catalog administration**: Manage the organization, views, labels, the API and MCP server catalog, and API workflows. + - **Consumption policy**: Manage subscription plans, key managers, and webhook subscribers. + - **Management API**: Drive the same operations programmatically, guarded per operation by `dp:*` scopes. + + **[Learn more](admin-settings/organization-settings.md)** + +## Improvements + +None. This is the first release, so there is no earlier behavior to improve on. + +## Compatible product versions + +The API Portal & MCP Hub shares a control plane with AI Workspace. The following product version was tested with this release: + +| Product | Compatible version | +|---------|--------------------| +| WSO2 AI Workspace | 1.0.0 | + +The distribution bundles the Platform API control plane so the local-auth quickstart works out of the box, and the two are versioned and shipped together—no separate compatibility check is needed. A production deployment that authenticates against an identity provider doesn't need it: the portal's only outbound call to a Platform API is the local-auth login. Full prerequisites are listed in the [Getting started](getting-started.md) guide. + +## Key changes + +None. There is no earlier release to migrate a deployment from. + +## Deprecations + +None. + +## Fixed issues + +None recorded against a released version, since this is the first release. + +## Known issues + +- [API Portal & MCP Hub](https://github.com/wso2/api-platform/issues?q=is%3Aissue+is%3Aopen+label%3AArea%2FDeveloperPortal) diff --git a/en/docs/api-portal/next/admin-settings/api-content.md b/en/docs/api-portal/next/admin-settings/api-content.md new file mode 100644 index 000000000..275f8b26d --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/api-content.md @@ -0,0 +1,121 @@ +--- +title: "Customize an API's content" +description: "Replace an API's generated overview page with your own Markdown or Handlebars body, set its icon, and attach downloadable documents." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/api-content/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/api-content.md +tags: + - cloud + - api-portal + - theming +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# Customize an API's content + +By default, an API's [overview page](../discover-apis/browse-apis.md#open-an-api) is generated from its specification—Endpoints, Resources, Scopes, and the subscription plans panel. **API content** lets you replace that body with your own, per API, and attach the images and documents that go with it. + +This is narrower than [theming](theming.md): a theme restyles every page in a view, while API content changes one API's overview body and leaves everything else alone. + +## What you can upload + +API content is a ZIP with up to two directories at its root: + +```text +my-api-content/ +├── web/ # the overview body, plus images +└── docs/ # downloadable documents, grouped into sections +``` + +At least one of the two must be present, or the upload is rejected. + +## Replace the overview body + +Put one of these files in `web/`: + +| File | Behavior | +|---|---| +| `apiContent.md` | Markdown, rendered to HTML and used as the overview body | +| `api-content.hbs` | A Handlebars template, for markup the portal renders directly | + +The portal looks for `apiContent.md` first, then `api-content.hbs`. Whichever it finds replaces the generated body entirely—the Endpoints, Resources, and Scopes sections stop appearing, so if you still want that information, include it yourself. With neither file present, the API falls back to the generated layout. + +The subscription plans panel is **not** part of the body, so it keeps rendering either way. + +!!! note + `web/` is read one level deep. Subdirectories inside it are ignored, so keep every file flat. + +### Accepted files in `web/` + +- **Text:** `.html`, `.hbs`, `.md`, `.json`, `.yaml`, `.yml` +- **Images:** `.svg`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.ico` + +Anything else is skipped silently. + +## Set the API icon + +Images in `web/` are registered under a tag taken from the filename without its extension. The catalog card and the overview header use the `api-icon` tag, so an image named `api-icon.png` in `web/` becomes that API's icon: + +```text +web/ +├── apiContent.md +└── api-icon.png # becomes the API's icon in the catalog and header +``` + +Without it, the portal falls back to a generated avatar showing the first two letters of the API's name. + +## Attach documents + +Files under `docs/` become the documents listed on the API's [documentation page](../discover-apis/browse-apis.md#read-the-specification-and-try-it), and are served to AI agents as raw Markdown. + +The **first-level directory name becomes the section heading** in the documentation navigation. Files placed directly in `docs/` land in a section called **Other**: + +```text +docs/ +├── getting-started.md # appears under "Other" +├── How to/ +│ ├── authenticate.md # appears under "How to" +│ └── paginate.md +└── Reference/ + └── error-codes.md # appears under "Reference" +``` + +## Upload it + +API content is the fourth step of the API wizard, and it's available only once the API exists—so create the API first, then reopen it to add content. + +1. Go to **Settings** and select **APIs** under **CONTENT** (or **MCP Servers**, for an MCP server). +2. Click the **⋮** menu on the API's row and select **Edit**. +3. Go to the **Content** step. +4. Drop in your ZIP and click **Upload content**. + +Uploading is immediate and independent of **Save changes** on the other steps. + +Files are merged rather than replaced wholesale: a file whose name matches one already stored overwrites it, and everything else is added. To remove a file, delete it through the [API Content](../rest-api/api-content.md) Management API. + +!!! note "The wrapper directory is optional here" + A single top-level folder containing `web/` and `docs/` is fine, and so is `web/` and `docs/` sitting at the ZIP root—the portal accepts both. This differs from a [theme ZIP](theming.md#package-the-theme), which requires the wrapper. + +## Preview before uploading + +In [design mode](design-mode.md), an API's content lives beside its manifest in the samples directory, so you can iterate on the body with a browser reload: + +```text +samples/apis/my-api-v1.0/ +├── api.yaml +├── definition.yaml +├── docs/ +│ └── getting-started.md +└── web/ + ├── apiContent.md + └── api-icon.png +``` + +## Related + +- [API Overview](../discover-apis/browse-apis.md#open-an-api): the generated page your content replaces +- [API Documentation](../discover-apis/browse-apis.md#read-the-specification-and-try-it): where `docs/` files surface +- [Theming](theming.md): restyle every page in a view, rather than one API's body +- [Manage APIs](manage-apis.md): the wizard this is the last step of +- [API Content](../rest-api/api-content.md): upload, replace, and delete content through the Management API diff --git a/en/docs/api-portal/next/admin-settings/apply-a-theme.md b/en/docs/api-portal/next/admin-settings/apply-a-theme.md new file mode 100644 index 000000000..71b501cc1 --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/apply-a-theme.md @@ -0,0 +1,54 @@ +--- +title: "Apply a theme to a view" +description: "Upload a theme ZIP to a view in the API Portal & MCP Hub, download the current theme, or reset to the built-in default." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/apply-a-theme/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/apply-a-theme.md +tags: + - cloud + - api-portal + - theming +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-23 +content_type: "how-to" +--- + +# Apply a theme + +The **Theming** tab in the API Portal's Settings page is where you upload a theme to a view, download what's currently applied, and roll back to the built-in default. + +This page covers the panel. To build the theme in the first place—which files you can override, how the color tokens work, and how to package the ZIP—see [Theming](theming.md). + +Theming is configured **per view**: if your organization has more than one [view](manage-views.md), use the view switcher at the top of the panel to pick which one you're theming. + +## Applying a custom theme + +1. Navigate to **Settings** and select the **Theming** tab under **APPEARANCE**. +2. Pick the view you want to theme, if you have more than one. +3. Under **Upload Theme**, drag and drop (or browse to) a single ZIP archive, up to 10 MB. + + The archive must hold **one wrapper directory** containing the theme's folders—`my-theme/styles/`, `my-theme/layout/`, `my-theme/partials/`, `my-theme/pages/`, `my-theme/images/`—not those folders at the archive root. See [Package the theme](theming.md#package-the-theme); getting this wrong makes the layout silently not apply. + +4. Click **Apply theme**. + +!!! warning "Uploading replaces the current theme entirely" + Applying a new ZIP replaces this view's existing custom theme files completely—it isn't merged with what was there before. Use **Download theme** first if you want a backup or a starting point to edit. + +The panel's **Current theme** indicator shows whether the view is using a custom uploaded theme or the built-in default. + +## Downloading the current theme + +Click **Download theme** to get a ZIP of what's currently applied to the view. If the view has no custom theme, this downloads the built-in default theme instead—so it always gives you a valid starting point to customize. + +## Resetting to the default theme + +Click **Reset to default** to discard the view's custom theme files and revert to the built-in default. This can't be undone—download a copy first if you might want it again. + +## API-level content + +Theming here controls the view-wide look and feel. To change one API's overview body, set its icon, or attach documents, see [Customize an API's Content](api-content.md). + +## Related + +- [Theming](theming.md): build a theme, and the sample you can start from +- [Design Mode](design-mode.md): preview a theme before uploading it +- [Manage Views](manage-views.md): themes are scoped per view diff --git a/en/docs/api-portal/next/admin-settings/design-mode.md b/en/docs/api-portal/next/admin-settings/design-mode.md new file mode 100644 index 000000000..3c7a2b02b --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/design-mode.md @@ -0,0 +1,233 @@ +--- +title: "Design Mode" +description: "Develop and preview API Portal layouts and themes offline, without a running database or identity provider." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/design-mode/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/design-mode.md +tags: + - cloud + - api-portal + - theming + - design-mode +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-05 +content_type: "how-to" +--- + +# Design mode + +Design mode lets you develop and preview API page layouts and org-level themes without a running database or an identity provider. The portal starts with sample data loaded from disk and serves all pages anonymously—login, API key generation, and subscription management are disabled. + +!!! danger "Local development only" + Design mode disables login, serves every page anonymously, and runs on plain HTTP with no TLS. Never expose an instance running in design mode to untrusted users or to a network you don't control. + +## When to use design mode + +Use design mode when you want to: + +- Iterate on a [theme](theming.md)—styles, layout, partials, or page templates—and see each change on reload +- Work on a page design without standing up a database, an identity provider, or a gateway +- Demo the portal against a fixed set of sample APIs, MCP servers, and applications + +## Enable design mode + +The section is commented out in `configs/config-template.toml`. Copy the whole block into your active `configs/config.toml` and uncomment it: + +```toml +[api_portal.design_mode] +enabled = true +path_to_layout = "./src/defaultContent/" # Handlebars templates and static assets +api_samples_path = "./samples/apis/" # directory of sample API definitions +mcp_samples_path = "./samples/mcps/" # directory of sample MCP server definitions +subscription_plans_path = "./samples/subscription-plans.yaml" +applications_path = "./samples/applications.yaml" +``` + +The sample paths are relative to the app's working directory, where the bundled `samples/` already lives—leave them as they are unless you're pointing at your own sample set. + +Then restart the API Portal: + +```bash +docker compose restart api-portal +``` + +Visit `http://localhost:9543/api-portal/views/default`. + +!!! note + The portal always starts on plain HTTP in design mode—no TLS certificate setup required. + +## Configuration options + +All six keys live under `[api_portal.design_mode]`: + +| Option (TOML) | Default | Description | +|---|---|---| +| `enabled` | `false` | Set to `true` to activate design mode | +| `api_samples_path` | `./samples/apis/` | Directory of sample REST/GraphQL/SOAP/WS API definitions | +| `mcp_samples_path` | `./samples/mcps/` | Directory of sample MCP server definitions | +| `subscription_plans_path` | `./samples/subscription-plans.yaml` | YAML file with org-level subscription plan details | +| `applications_path` | `./samples/applications.yaml` | YAML file with sample applications shown on the Applications page | +| `path_to_layout` | `./src/defaultContent/` | Layout directory used to render pages | + +!!! note + There's no `APIP_AP_*` environment variable that maps onto these keys. To drive one from the environment, write an interpolation token into `config.toml`—{% raw %}`path_to_layout = '{{ env "MY_THEME_DIR" "./src/defaultContent/" }}'`{% endraw %}. See [Configurations](../references/configurations.md). + +## Working on a theme + +Design mode exists mainly to iterate on a theme without a running stack. Point `path_to_layout` at your theme directory, edit files, and reload the browser—no server restart needed. + +```toml +[api_portal.design_mode] +enabled = true +path_to_layout = "./my-theme/" +``` + +To preview the example theme from [Theming](theming.md#example-a-teal-and-coral-theme), use `./samples/layouts/green-theme/`. + +For what a theme contains, which files you can override, how the color tokens work, and how to package one for upload, see [Theming](theming.md). Design mode and the production theme upload use the same directory structure, so a theme built here deploys without conversion. + +## Sample APIs and MCP servers + +APIs and MCP servers live in separate directories. The directory name becomes the handle used in the URL. + +``` +samples/ +├── apis/ # REST, GraphQL, SOAP, WS APIs → /views/default/apis +│ ├── ping-api-v1.0/ +│ │ ├── api.yaml +│ │ ├── definition.yml +│ │ └── docs/ +│ └── … +└── mcps/ # MCP servers → /views/default/mcps + ├── travel-assistant-mcp-v1/ + │ ├── api.yaml + │ ├── definition.yaml + │ └── docs/ + └── … +``` + +### `api.yaml` format (REST API) + +```yaml +apiVersion: api-portal.api-platform.wso2.com/v1 +kind: RestApi + +metadata: + name: my-api-v1.0 # used as the URL handle: /views/default/api/my-api-v1.0 + +spec: + type: REST # REST | WS | GRAPHQL | SOAP | WEBSUB + displayName: My API + version: v1.0 + description: A short description shown on the API card. + status: PUBLISHED + + tags: + - payments + + subscriptionPlans: # leave empty [] if no subscription plans + - Gold + - Silver + + endpoints: + sandboxUrl: http://localhost:8080/my-api + productionUrl: https://api.example.com/my-api +``` + +### `api.yaml` format (MCP server) + +```yaml +apiVersion: api-portal.api-platform.wso2.com/v1 +kind: MCP + +metadata: + name: my-mcp-v1.0 + +spec: + type: MCP + displayName: My MCP Server + version: 1.0.0 + description: MCP server exposing tools for AI agents. + status: PUBLISHED + + tags: + - mcp + + subscriptionPlans: + - Gold + + endpoints: + productionUrl: https://mcp.example.com +``` + +The `definition.yaml` alongside `api.yaml` defines the tools, resources, and prompts the server exposes, as a flat list of type-tagged entries: + +```yaml +- type: TOOL + name: search_flights + description: Search for available flights between two cities. + inputSchema: + type: object + properties: + origin: { type: string } + destination: { type: string } +``` + +### Live reload + +The portal re-reads API definitions from disk on every page request. Edit `api.yaml` or any doc file, then reload the browser—no server restart needed. + +## Sample applications + +The Applications page is available in design mode and shows entries from `applications_path`. The format follows the same Kubernetes-style manifest used across all sample files: + +```yaml +apiVersion: api-portal.api-platform.wso2.com/v1 +kind: ApplicationList +items: + + - metadata: + name: my-app + + spec: + displayName: My App + description: A short description shown on the application card. +``` + +`metadata.name` becomes the application ID. Creating new applications, viewing individual application details, and managing keys aren't available in design mode. + +## What is disabled in design mode + +Design mode turns off everything that needs a database or an identity provider: + +| Feature | Status | +|---|---| +| Login / IDP authentication | Disabled—all pages are served anonymously | +| API subscriptions | Disabled | +| Creating / deleting applications | Disabled—Applications page is read-only | +| Individual application details | Disabled | +| API key generation | Disabled | +| Database | Not required—no connection is attempted | +| TLS / HTTPS | Not used—server always starts on plain HTTP | + +## Turning design mode off + +Set `enabled = false` (or remove the section entirely) in `configs/config.toml`: + +```toml +[api_portal.design_mode] +enabled = false +``` + +Then restart: + +```bash +docker compose restart api-portal +``` + +The portal returns to production mode, requiring a database and (if configured) an IDP. + +## Related + +- [Theming](theming.md): what a theme contains, the color tokens, and how to package one +- [Apply a Theme](../admin-settings/theming.md): upload a finished theme to a view +- [Configurations](../references/configurations.md): the full `config.toml` reference diff --git a/en/docs/api-portal/next/admin-settings/key-manager-integration.md b/en/docs/api-portal/next/admin-settings/key-manager-integration.md new file mode 100644 index 000000000..0974bcccc --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/key-manager-integration.md @@ -0,0 +1,45 @@ +--- +title: "Configure key managers in the API Portal & MCP Hub" +description: "Register the OAuth2 key managers applications can obtain access tokens from, using the API Portal Settings page." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/key-manager-integration/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/key-manager-integration.md +tags: + - cloud + - api-portal + - key-manager +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-23 +content_type: "how-to" +--- + +# Key managers + +A **key manager** is the OAuth2 authorization server used to issue access tokens for OAuth2-secured APIs. The API Portal & MCP Hub never creates or stores OAuth applications—developers create their OAuth application directly in the key manager, then link its client ID to an application in the portal. The portal only proxies `client_credentials` token requests to the key manager's token endpoint; it never sees or stores a client secret. + +## Adding a key manager + +1. Navigate to **Settings** and select the **Key Managers** tab under **ORGANIZATION**. +2. Click **+ Add key manager**. +3. Fill in the fields: + +| Field | Description | +|---|---| +| **Name** | Required. The name shown to developers on the Manage Keys card, for example `Production` | +| **Token endpoint** | Required. The OAuth2 token endpoint the portal proxies `client_credentials` requests to | +| **Enabled** | Whether the key manager is available for developers to select. Disable instead of deleting to take it out of use temporarily | + +The portal generates the key manager's internal identifier itself. A key manager created through the [Management API](../rest-api/key-managers.md) can supply one as `id`; one created here gets a UUID. + +4. Click **Add key manager**. + +## Editing or deleting a key manager + +Click a key manager's name (or the pencil icon) to edit it. Click the trash icon to delete it—applications relying on it will no longer be able to obtain tokens through the portal; this can't be undone. + +## How developers use key managers + +1. The developer creates an OAuth application directly in the key manager's own console (outside the portal) and obtains a client ID. +2. In the portal, the developer opens their application's **Manage Keys** view, picks a key manager, and pastes the client ID. The portal stores only the client ID—never a secret. +3. To get an access token, the developer clicks **Generate access token**, enters the client secret when prompted, and the portal proxies a `client_credentials` request to the key manager's token endpoint. The secret is never stored. + +When an application is deleted, the portal removes the stored client ID mappings—it doesn't contact the key manager, since the OAuth application itself is owned and managed there independently. diff --git a/en/docs/api-portal/next/admin-settings/llm-instructions.md b/en/docs/api-portal/next/admin-settings/llm-instructions.md new file mode 100644 index 000000000..091958ff5 --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/llm-instructions.md @@ -0,0 +1,79 @@ +--- +title: "Configure LLM instructions for the API Portal & MCP Hub" +description: "Set the portal name and description that head the llms.txt file, and turn AI discoverability on or off for the whole portal." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/llm-instructions/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/llm-instructions.md +tags: + - cloud + - api-portal + - ai-discovery +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# LLM instructions + +**LLM Instructions** controls how AI agents see your portal as a whole. The name refers to large language models (LLMs), the systems behind those agents. It sets the name and description at the top of `llms.txt`—the first thing an agent reads before it navigates anything else—and carries the switch that turns AI discoverability on or off for the entire portal. + +Everything on this page is scoped to one [view](manage-views.md). Use the view selector to switch. + +## Open the page + +Go to **Settings** and select **LLM Instructions** under **AI & DISCOVERY**. + +The page puts the editable fields beside a live preview of the file they produce: + +![LLM Instructions page with the AI-discoverable toggle, portal name and description fields, and a live llms.txt preview](../../../assets/img/devportal/llm-instructions.png) + +## Turn AI discoverability on or off + +The **Portal is AI-discoverable** card at the top shows the view's live `llms.txt` path and a toggle. + +Switching it off makes every agent-facing endpoint return `404`—`llms.txt` itself, the API and MCP catalogs, per-artifact Markdown, raw specifications, and every workflow endpoint. It overrides each artifact's own agent-visibility setting, so nothing reaches an agent while it's off. + +The **Publish** button stays clickable when the toggle is off, so you can save the change. + +## Set the portal identity + +Two fields make up the instructions, and both appear verbatim at the top of `llms.txt`. + +| Field | What it does | +|---|---| +| **Portal name** | Replaces the default `{orgName} API Portal` heading | +| **Description** | The paragraph below the name. This is the orientation text agents read first | + +The description is a free-text area, not a one-liner. Use it for the context an agent can't infer from individual API specs: + +- What the portal covers and what kinds of APIs it exposes +- How the catalog is organized—by domain, team, or lifecycle stage +- Authentication conventions that apply across APIs +- Which workflows are the recommended starting points for common tasks +- Usage policies or limitations agents should respect + +Leave either field empty and the portal falls back to its defaults: `{orgName} API Portal` for the heading, and a generic one-line description. + +Click **Publish** to save. Changes take effect immediately—the next request for `llms.txt` reflects them. + +## Preview the result + +The pane beside the fields renders the `llms.txt` your settings produce, and refreshes as you type, so you can see the header in context above the generated API and workflow index. The icon in its corner opens the live `llms.txt` in a new tab. + +To fetch it yourself: + +```text +GET /api-portal/{orgName}/views/{viewName}/llms.txt +``` + +The generated file lists each agent-visible artifact under its own section: + +![Generated llms.txt showing an API Workflows section with workflow names, descriptions, and links](../../../assets/img/devportal/llms-txt.png) + +Only your name and description are editable. Everything below them—the API Workflows, APIs, MCPs, GraphQL, WebSocket, and WebSub sections—is generated from the catalog and reflects each artifact's own agent visibility. See [AI Agent Discovery](../ai-agent-discovery.md) for how that index is built. + +## Related + +- [AI Agent Discovery](../ai-agent-discovery.md): every agent-facing endpoint, and what `llms.txt` contains +- [Managing API Workflows](manage-api-workflows.md): per-workflow agent visibility +- [Make an API AI-Ready](../../../cloud/develop-api-proxy/make-api-ai-ready.md): per-API agent visibility, and writing descriptions agents can use +- [Manage Views](manage-views.md): why these settings are per-view diff --git a/en/docs/api-portal/next/admin-settings/manage-api-workflows.md b/en/docs/api-portal/next/admin-settings/manage-api-workflows.md new file mode 100644 index 000000000..416464409 --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/manage-api-workflows.md @@ -0,0 +1,113 @@ +--- +title: "Manage API workflows in the API Portal & MCP Hub" +description: "Author an API workflow as an Arazzo spec or Markdown, generate its agent prompt, and publish it to a view." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-api-workflows/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-api-workflows.md +tags: + - cloud + - api-portal + - api-workflows +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# Manage API workflows + +As a portal admin, you author API workflows, generate the prompt that lets AI agents execute them, and publish them to a view. This page covers the admin side; for what consumers and agents then see, read [API Workflows](../api-workflows.md). + +## Open the workflows panel + +Go to **Settings** and select **API Workflows** under **AI & DISCOVERY**. The panel lists every workflow in the selected view—use the view selector above the list to switch views, since workflows are scoped to one view. + +Each card shows: + +- A status dot and label: **DRAFT** or **PUBLISHED** +- A content type badge: **ARAZZO** or **MD** +- The name and description +- Hover actions: view the agent prompt (only when the workflow is agent-visible), edit, and delete + +Click **Create** to open the authoring wizard. + +## Author a workflow + +The wizard has three steps, with a live preview beside each one. + +### Step 1: Basic information + +| Field | Notes | +|---|---| +| **Name** | Required, up to 120 characters | +| **Handle** | The lowercase identifier used in URLs. Auto-generated from the name; edit it here if you want a specific one. **It can't be changed after the workflow is created** | +| **Description** | Required. This is what agents read when deciding whether the workflow fits their task, so describe the goal, not the mechanics | +| **Agent visibility** | **Visible to agents** or **Hidden from agents** | + +The right pane shows an **Agent Preview**—the name and description exactly as they'll appear in agent discovery. + +### Step 2: Define the API workflow + +Choose one of two authoring paths. + +**Upload file**—drop in a workflow you already have. Arazzo specs go in as `.yaml`, `.yml`, or `.json`; a natural-language workflow goes in as `.md`. After upload, an Arazzo file gets its source descriptions validated and listed, and a Markdown file gets a summary strip. + +**From template**—pick the APIs that take part in the workflow and let the portal generate a starter Arazzo spec from their specifications. Filter the API list by name, type, or description, or narrow it to selected, AI Ready, or AI Restricted APIs. MCP servers appear in this list alongside APIs. + +Either way, the right pane holds the definition editor: + +- A **Format** toggle between **Arazzo Spec** (`workflow.arazzo.yaml`) and **Markdown** (`workflow.md`) +- **Open in Claude**, **Download .arazzo.yaml**, and **Copy Prompt**—for refining the generated template outside the portal. All three need at least one API selected +- An **Upload** control to bring an edited file back in + +!!! tip + Source descriptions are what let an agent find each API's specification while following the workflow. Generating from a template fills them in with the specs of APIs published in this portal; a hand-written Arazzo file has to declare them itself. + +### Step 3: Agent prompt + +The agent prompt is **generated from the workflow**, not written from scratch. Three actions are available: + +- **Regenerate** rebuilds the prompt after you change the definition. +- Editing the text directly lets you adjust the generated wording. +- **Copy prompt** puts it on your clipboard to use elsewhere. + +The right pane shows the Markdown file agents will fetch, plus a **Readiness** checklist covering name and description, workflow definition, and agent prompt. + +If you set the workflow to hidden from agents in step 1, this step shows a banner saying so, with a shortcut back to change it. + +## Publish or save as a draft + +The wizard's footer carries a split button: + +- **Publish Flow** makes the workflow live in its view immediately. +- **Save as Draft**, from the dropdown, saves it without exposing it to consumers. + +Draft workflows appear nowhere outside this panel—not in the portal gallery, not in `llms.txt`, not in `api-workflows.md`. + +To unpublish, reopen the workflow and save it as a draft again. There's no separate unpublish action. + +## Control visibility + +A workflow has exactly one visibility setting, plus its publication status. The two combine like this: + +| Status | Agent visibility | In the portal gallery | In `llms.txt` and the agent endpoints | +|---|---|---|---| +| Published | Visible | Yes | Yes | +| Published | Hidden | Yes | No | +| Draft | Either | No | No | + +Hiding a workflow from agents is how you ship it to human developers while its automated execution is still being validated. There is no separate control for hiding a published workflow from people—publishing it makes it visible in the gallery for that view. + +!!! note + The portal-wide **Portal is AI-discoverable** toggle overrides all of this. With it off, no workflow reaches an agent regardless of its setting. See [LLM Instructions](llm-instructions.md). + +## Edit and delete + +Click the pencil on a card to reopen the wizard with the workflow's current values. The handle is fixed; everything else can change, including the content type—you can replace an Arazzo definition with Markdown or the reverse. + +Click the trash icon to delete a workflow. Deleting removes it from the gallery and from every agent-facing endpoint. + +## Related + +- [API Workflows](../api-workflows.md): what consumers and agents see, and the endpoints they use +- [LLM Instructions](llm-instructions.md): the portal-wide AI discoverability toggle and the `llms.txt` header +- [AI Agent Discovery](../ai-agent-discovery.md): every agent-facing endpoint the portal serves +- [Artifact types](../setting-up/artifact-types.md): whether this deployment serves API workflows at all diff --git a/en/docs/api-portal/next/admin-settings/manage-apis.md b/en/docs/api-portal/next/admin-settings/manage-apis.md new file mode 100644 index 000000000..026bb9747 --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/manage-apis.md @@ -0,0 +1,86 @@ +--- +title: "Manage APIs in the API Portal & MCP Hub" +description: "Add, edit, publish, deprecate, and delete the REST, WebSocket, GraphQL, WebSub, and SOAP APIs visible in the API Portal & MCP Hub." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-apis/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-apis.md +tags: + - cloud + - api-portal + - publish-apis +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-23 +content_type: "how-to" +--- + +# Manage APIs + +The **APIs** tab in the API Portal's Settings page is where you add, edit, publish, deprecate, and delete the APIs visible in the portal catalog. + +## Adding an API + +1. Navigate to **Settings** and select the **APIs** tab under **CONTENT**. +2. Click **+ Add API**. A four-step wizard opens. + +### Step 1: Details + +| Field | Description | +|---|---| +| **API name** | Required | +| **Version** | Required (for example, `v1.0`) | +| **Handle (URL slug)** | Auto-generated from name and version; edit to override | +| **API type** | `REST`, `WebSocket`, `GraphQL`, `WebSub`, or `MCP` | +| **Agent visibility** | **Visible** includes the API in `llms.txt` and AI-agent discovery surfaces; **Hidden** excludes it while still showing it to human users | +| **Description** | Required—shown in the catalog and used as context for AI agents | +| **Production URL** | Required for most API types | +| **Sandbox URL** | Optional | +| **Labels** | A toggle picker of the labels defined in your organization. Selected labels control which [views](manage-views.md) show this API. Empty until you create labels under [Manage Labels](manage-labels.md) | +| **Tags** | Comma-separated free-text keywords for search and discovery. Unlike labels, they don't affect visibility | +| **Status** | **Published** or **Deprecated** | +| **Technical owner / email**, **Business owner / email** | Ownership contacts shown on the API's detail page | +| **Applicable Subscription Plans** | Search and select which [subscription plans](subscription-plans.md) developers can subscribe under. Leave empty to make the API accessible without a plan | + +Click **Next**. + +### Step 2: Spec + +Upload the contract that defines the API. One file is required, and the format follows the API type you chose in step 1: + +| API type | Contract | +|---|---| +| REST | OpenAPI, as `.json`, `.yaml`, or `.yml` | +| WebSocket, WebSub | AsyncAPI, as `.json`, `.yaml`, or `.yml` | +| GraphQL | A GraphQL schema, as `.graphql` or `.gql` | +| MCP | A flat, type-tagged list of tools, resources, and prompts, as `.yaml` or `.yml`. See [Manage MCP servers](manage-mcp-servers.md#adding-an-mcp-server) | + +The file picker also accepts `.wsdl` and `.xml`, but **API type** offers no SOAP option, so you can't create a SOAP API here. Create those through the [Management API](../rest-api/apis.md) instead, posting `type: SOAP` with a WSDL definition. + +Click **Next**. + +### Step 3: Documentation + +Optionally upload one or more Markdown files (`.md` or `.markdown`) as guides or reference docs for developers. These are what the portal serves as attached documents, both on the API's documentation page and to AI agents. + +Click **Next** to create the API, or continue to Step 4 if you're editing an existing API. + +### Step 4: Content (edit mode only) + +Once an API exists, you can upload a ZIP holding a `web/` folder (the overview body and images), a `docs/` folder (downloadable documents), or both. Files with a matching name are replaced; others are added. + +Click **Upload content**—this uploads immediately and is independent of **Save changes**. + +For the file names the portal looks for, how the API icon is set, and how `docs/` subdirectories become documentation sections, see [Customize an API's Content](api-content.md). + +## Editing an API + +Click the **⋮** menu on an API's row and select **Edit** to reopen the wizard, prefilled with its current details, spec, documentation, and content. + +## Publishing and deprecating + +Use the **⋮** menu on an API's row: + +- **Publish**—makes a draft or deprecated API live in the catalog +- **Deprecate**—keeps the API visible but marked as deprecated; you can publish it again at any time + +## Deleting APIs + +Use the **⋮** menu's **Delete** action to remove a single API, or select multiple rows with the checkboxes to reveal a bulk action bar with **Delete selected**. Deleting an API removes it from the portal along with its spec and documentation—this can't be undone. diff --git a/en/docs/api-portal/next/admin-settings/manage-labels.md b/en/docs/api-portal/next/admin-settings/manage-labels.md new file mode 100644 index 000000000..8dbe57f9e --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/manage-labels.md @@ -0,0 +1,42 @@ +--- +title: "Manage labels in the API Portal & MCP Hub" +description: "Create, edit, and delete labels used to group APIs so that views can control which APIs are visible to consumers." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-labels/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-labels.md +tags: + - cloud + - api-portal + - labels +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-23 +content_type: "how-to" +--- + +# Manage labels + +A **label** is a tag assigned to APIs so that [views](manage-views.md) can control which APIs they make visible. An API tagged `internal` only appears in views that include the `internal` label. + +## Adding a label + +1. Navigate to **Settings** and select the **Labels** tab under **ORGANIZATION**. +2. Click **+ Add label**. +3. Fill in the fields: + +| Field | Description | +|---|---| +| **Display name** | Human-friendly label name shown in the portal UI | +| **Name** | Lowercase identifier used internally and matched against view label lists | + +4. Click **Add label**. + +## Editing a label + +Click a label's display name (or the pencil icon) in the **Labels** table, update the display name or name, and save. + +## Deleting a label + +Click the trash icon next to a label and confirm. Deleting a label removes it from every API it's currently assigned to—this can't be undone. + +## Applying labels + +Assign labels to an API from the **Labels & Visibility** section of the [Manage APIs](manage-apis.md) wizard, then attach the same labels to a view under [Manage Views](manage-views.md) to control where the API appears. diff --git a/en/docs/api-portal/next/admin-settings/manage-mcp-servers.md b/en/docs/api-portal/next/admin-settings/manage-mcp-servers.md new file mode 100644 index 000000000..4775c04cd --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/manage-mcp-servers.md @@ -0,0 +1,60 @@ +--- +title: "Manage MCP servers in the API Portal & MCP Hub" +description: "Add, edit, publish, deprecate, and delete the MCP servers exposed in the API Portal & MCP Hub." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-mcp-servers/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-mcp-servers.md +tags: + - cloud + - api-portal + - mcp +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-23 +content_type: "how-to" +--- + +# Manage MCP servers + +The **MCP Servers** tab in the API Portal's Settings page manages the Model Context Protocol (MCP) servers the portal exposes. From it you can add, edit, publish, deprecate, and delete a server, singly or in bulk. + +MCP servers sit in the same catalog as your REST, WebSocket, GraphQL, and WebSub APIs. + +## Adding an MCP server + +1. Navigate to **Settings** and select the **MCP Servers** tab under **CONTENT**. +2. Click **+ Add MCP Server**. This opens the same four-step wizard used for [Manage APIs](manage-apis.md), with **API type** preset to **MCP**. +3. Complete the **Details**, **Spec**, and **Documentation** steps as described in [Manage APIs](manage-apis.md#adding-an-api). + +One step differs from a regular API. At **Spec**, an MCP server's contract isn't an OpenAPI document—it's a flat, type-tagged list of the tools, resources, and prompts the server exposes: + +```yaml +- type: TOOL + name: search_flights + description: Find available flights for a given origin, destination, and travel date. + inputSchema: + type: object + properties: + from: { type: string } + to: { type: string } +``` + +`TOOL`, `RESOURCE`, and `PROMPT` entries can be mixed in one file. What you upload here becomes the server's Tools, Resources, and Prompts sections once the server is published. It reaches AI agents only while the server is also agent-visible. + +Once the server is created, an additional **Content** step lets you upload a ZIP for its landing-page content, exactly as with a regular API. + +## Editing, publishing, and deleting + +MCP servers share the same row-level actions as APIs: + +- Click the **⋮** menu and select **Edit** to reopen the wizard with the server's current details. +- Select **Publish** or **Deprecate** to control its catalog visibility. +- Select **Delete** to remove a single server, or use the checkboxes to select multiple servers and **Delete selected** from the bulk action bar. This removes the server along with its spec and documentation and can't be undone. + +## What developers see + +Once published, a server appears in the **MCP Servers** catalog with its tools, resources, and prompts listed, an MCP Playground for trying them, and a client configuration snippet. See [MCP Servers](../mcp-servers/overview.md). + +Servers can also be published without the wizard, through the portal's [MCP Registry API](../mcp-registry.md). + +## Artifact types + +If you only want to expose MCP servers (hiding every other API type entirely), or the reverse, see [Artifact types](../setting-up/artifact-types.md). diff --git a/en/docs/api-portal/next/admin-settings/manage-views.md b/en/docs/api-portal/next/admin-settings/manage-views.md new file mode 100644 index 000000000..6312e94e6 --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/manage-views.md @@ -0,0 +1,45 @@ +--- +title: "Manage views in the API Portal & MCP Hub" +description: "Create, edit, and delete views to scope which labelled APIs are visible to different audiences in the API Portal & MCP Hub." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-views/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/manage-views.md +tags: + - cloud + - api-portal + - views +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-23 +content_type: "how-to" +--- + +# Manage views + +A **view** is a filtered, branded subset of APIs—for example, `public` for external developers and `internal` for internal teams. Each view has its own URL (`/api-portal/<orgHandle>/views/<viewName>`) and shows only the APIs tagged with its assigned labels. [LLM Instructions](llm-instructions.md) and [API Workflows](manage-api-workflows.md) are also configured per view. + +## Adding a view + +1. Navigate to **Settings** and select the **Views** tab under **ORGANIZATION**. +2. Click **+ Add view**. +3. Fill in the fields: + +| Field | Description | +|---|---| +| **Handle** | Lowercase identifier used in the view's URL. Can't be changed later | +| **Name** | Human-friendly name shown in the portal header | +| **Labels** | Click to toggle which labels this view includes. Only APIs carrying at least one of these labels appear in the view | + +4. Click **Add view**. + +!!! note + If no labels exist yet, create them first under [Manage Labels](manage-labels.md)—the label picker is empty until at least one label exists. + +## Editing a view + +Click a view's name (or the pencil icon) in the **Views** table to reopen the same form, update its name or label selection, and click **Save changes**. The handle can't be changed after creation. + +## Deleting a view + +Click the trash icon next to a view and confirm. Deleting a view also removes its LLM Instructions and API Workflows—this can't be undone. + +!!! note + The `default` view can't be deleted. diff --git a/en/docs/api-portal/next/admin-settings/organization-settings.md b/en/docs/api-portal/next/admin-settings/organization-settings.md new file mode 100644 index 000000000..cd0535621 --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/organization-settings.md @@ -0,0 +1,39 @@ +--- +title: "Configure organization settings in the API Portal & MCP Hub" +description: "Edit the current organization's display name, business owner contact, and identity provider reference from the API Portal Settings page." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/organization-settings/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/organization-settings.md +tags: + - cloud + - api-portal + - organization +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-23 +content_type: "how-to" +--- + +# Organization settings + +The **Organization** tab in the API Portal's Settings page manages the details of the organization you're signed in to. + +## Editing organization details + +1. Log in to the API Portal & MCP Hub as an admin and navigate to **Settings**. +2. Under the **ORGANIZATION** group, select the **Organization** tab. +3. Update any of the following fields: + +| Field | Description | +|---|---| +| **Name** | The display name shown throughout the portal UI | +| **Handle** | The URL-safe identifier used in every portal URL (`/api-portal/<orgHandle>/views/<viewName>`). Read-only—it can't be changed after the organization is created | +| **Artifact types served** | Read-only. Shows which artifact types the portal serves—APIs, Model Context Protocol (MCP) servers, and API workflows. Set by the operator in the `[api_portal.artifacts]` config, not from this pane; pages for a type that isn't served return 404. See [Artifact types](../setting-up/artifact-types.md) | +| **Business owner** | Contact name for the organization owner | +| **Business owner contact** | The owner's phone number or other contact string | +| **Business owner email** | The owner's email address | +| **IDP reference ID** | The organization claim value your identity provider (IDP) asserts at single sign-on (SSO) login, which incoming tokens are resolved against. Effectively read-only—the portal sets it to the organization handle when the organization is created, and saving a different value fails, because it's what every token is matched against. To have an IDP's organization claim resolve here, align it with the handle instead: see [Connect an identity provider](../setting-up/authentication/connect-an-identity-provider.md#step-5-make-the-organization-claim-resolve-to-your-organization) | +| **Control plane reference ID** | Reference ID included in outbound webhook event payloads (`org.ref_id`). Not used for authentication | + +4. Click **Save changes**. + +!!! note + This page only manages the organization you're currently in—it doesn't create or delete organizations. Organization creation happens once, automatically, when the portal is provisioned. diff --git a/en/docs/api-portal/next/admin-settings/subscription-plans.md b/en/docs/api-portal/next/admin-settings/subscription-plans.md new file mode 100644 index 000000000..3ace4d1ef --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/subscription-plans.md @@ -0,0 +1,56 @@ +--- +title: "Configure subscription plans in the API Portal & MCP Hub" +description: "Define the rate and quota tiers that applications can subscribe to, and attach them to your published APIs." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/subscription-plans/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/subscription-plans.md +tags: + - cloud + - api-portal + - subscription-plans +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-23 +content_type: "how-to" +--- + +# Subscription plans + +A **subscription plan** is a named usage tier that controls how much of an API a developer can consume. You attach one or more plans to each API you publish, and developers choose a plan when they subscribe. + +## Adding a plan + +1. Navigate to **Settings** and select the **Subscription Plans** tab under **ORGANIZATION**. +2. Click **+ Add plan**. +3. Fill in the plan details: + +| Field | Description | +|---|---| +| **Name** | Required. The name shown to developers on the plan card, for example `Gold` | +| **Description** | Optional description of the tier shown to developers | +| **Limits** | One or more usage limits (see below). Leave empty for an unlimited plan | +| **External reference ID** | Optional universally unique identifier (UUID) linking this plan to an external billing or quota system | + +A plan carries three separate identifiers, and it's worth keeping them apart: + +- **Plan handle**—the developer-facing id used in Management API paths and in the `subscriptionPlans` references on an API. Supply it as `id` when creating a plan through the [Management API](../rest-api/subscription-plans.md). This form sends no `id`, so plans created here get a generated UUID as their handle. +- **Database UUID**—the portal's own internal primary key. Never exposed in the API. +- **External reference ID**—the optional link to a billing or quota system, set in the field above. It has no effect inside the portal. + +4. Click **+ Add limit** for each limit you want to enforce, and configure: + +| Field | Description | +|---|---| +| **Type** | `REQUEST_COUNT`, `EVENT_COUNT` (for async/webhook APIs), `BANDWIDTH`, or `TOTAL_TOKEN_COUNT` | +| **Count** | Maximum allowed. Use `-1` for unlimited | +| **Per / Unit** | A time amount and unit (`MINUTE`, `HOUR`, `DAY`, `MONTH`), or "— no window —" for a limit with no time window | + +5. Click **Add plan**. + +## Editing or deleting a plan + +Click a plan's name (or the pencil icon) to edit its fields and limits. + +Click the trash icon to delete a plan. Deleting can't be undone. Any subscription held under a deleted plan has to be moved to a different plan before it can renew. + +## Attaching plans to an API + +Subscription plans aren't automatically available on every API. When creating or editing an API in [Manage APIs](manage-apis.md), select which plans apply under **Applicable Subscription Plans**. If no plans are attached, the API is accessible without a subscription plan. diff --git a/en/docs/api-portal/next/admin-settings/theming.md b/en/docs/api-portal/next/admin-settings/theming.md new file mode 100644 index 000000000..ff84c1171 --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/theming.md @@ -0,0 +1,197 @@ +--- +title: "Theming the API Portal & MCP Hub" +description: "Build a custom theme for a view—override styles, layouts, partials, and pages, re-color the portal from a few seed variables, and package it for upload." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/theming/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/theming.md +tags: + - cloud + - api-portal + - theming +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# Theming + +A theme changes how a [view](manage-views.md) looks and behaves: its colors, page shell, header and footer, and the markup of individual pages. You build one as a directory of files, package it as a ZIP, and an admin applies it to a view. + +Theming is scoped **per view**, so one organization can serve a branded partner portal and a plain internal one from the same catalog. + +## How a theme works + +The portal renders every page from a complete default template tree at `src/defaultContent/`. A theme is a **partial copy of that tree**: include only the files you want to change, and every file you leave out is served from the default automatically. + +That has two consequences worth internalizing: + +- **A theme is not just colors.** Any file under `src/defaultContent` can be overridden—this includes the page shell, partials, and the markup of individual pages. +- **You never fork the whole tree.** A color-only theme is one file. Adding a custom API listing is two. + +### What you can override + +These are the files the example theme replaces, and what each one governs: + +| Path in the theme | Controls | +|---|---| +| `styles/main.css` | The portal-wide stylesheet, including the color tokens | +| `layout/main.hbs` | The outer HTML shell—`<head>`, page `<title>`, nav frame | +| `partials/header.hbs` | The top bar | +| `partials/footer.hbs` | The footer | +| `pages/home/partials/home.hbs` | The home page body | +| `pages/apis/partials/api-listing.hbs` | How APIs are listed, including the card markup | +| `pages/api-landing/partials/api-detail-banner.hbs` | The header block on an API's overview page | +| `images/` | Logo and other image assets. Optional, and the example below ships none | + +Any other `pages/**/partials/*.hbs` file from the default tree works the same way—those are just the ones the example below uses. + +!!! note + JavaScript can't be added as a theme asset, and templates are validated on upload. Portal behavior comes from the portal's own scripts; a theme covers CSS, Handlebars templates, and images. + +## Colors come from a few seeds + +`styles/main.css` defines its palette as a small set of **seed** variables, with everything else **derived** from them through `color-mix()`. Change the seeds and the whole portal re-colors—text ramp, borders, surfaces, gradients, the dark hero and sidebar included. + +The seeds are: + +```css +:root { + /* SEEDS · brand — these cascade everywhere */ + --primary: #1a4c6d; /* structure, links, focus, primary buttons */ + --primary-dark: #043556; /* primary hover / pressed / deep panels */ + --primary-light: #2b719f; /* lifted primary states, on-dark highlights */ + --accent: #fe8c3a; /* solid highlights, badges, env dots, borders */ + --accent-dark: #ef4223; + --accent-light: #ff8636; + + /* SEEDS · neutral foundation */ + --ink: #1a2433; /* darkest text — generates the whole grey ramp */ + --surface: #ffffff; /* page background — the ramp mixes toward this */ + --white: #ffffff; /* fills on colored or dark surfaces */ + + /* SEEDS · semantic — deliberately independent of the brand */ + --success: #2e7d32; + --warning: #e39a00; + --danger: #c62828; + --info: #0277bd; +} +``` + +Everything below them in the file is derived, for example: + +```css +--text: color-mix(in srgb, var(--ink) 80%, var(--surface)); +--border: color-mix(in srgb, var(--ink) 12%, var(--surface)); +--focus-ring: color-mix(in srgb, var(--primary) 40%, transparent); +``` + +So a re-color means editing the seed block and nothing else. + +!!! important "Copy `main.css` whole" + Don't replace `main.css` with just a `:root` override. It carries both the variables *and* the rule definitions, plus the `@import` statements that pull in the other stylesheets (`home.css`, `header.css`, and the rest). Those imported files are served from the defaults and don't need copying—but the file that imports them does. + +## Example: a teal and coral theme + +Here's a complete theme that re-colors the portal and replaces the wordmark, home hero, and API listing. It's seven files: + +```text +my-theme/ +├── styles/main.css # seeds changed, plus a few custom rules +├── layout/main.hbs # page shell and <title> +├── partials/header.hbs # text wordmark instead of the logo image +├── partials/footer.hbs # custom footer links +└── pages/ + ├── home/partials/home.hbs # custom hero copy + ├── apis/partials/api-listing.hbs # hero, plus a different card type and arrangement + └── api-landing/partials/api-detail-banner.hbs # custom API overview header +``` + +Everything else—the other pages, the sidebar, the imported stylesheets—is served from the defaults untouched. + +### The color change + +`styles/main.css` is a copy of the default with its seed block swapped. That single edit is the whole re-color: + +```css +:root { + /* SEEDS · brand — teal and coral instead of the default navy and orange */ + --primary: #0f766e; + --primary-dark: #134e4a; + --primary-light: #2dd4bf; + --accent: #fb7185; + --accent-dark: #e11d48; + --accent-light: #fda4af; + + /* SEEDS · neutral foundation — --ink generates the whole grey ramp */ + --ink: #0e2a2a; + --surface: #ffffff; + --white: #ffffff; +} +``` + +The dark hero, the sidebar gradient, borders, and the text ramp all follow from those, because they're derived rather than hardcoded. + +### Replacing the wordmark + +`partials/header.hbs` swaps the logo image for text and an icon, which shows that branding doesn't require shipping a new asset: + +```handlebars +<a class="navbar-brand d-flex align-items-center brand-wordmark" href="{{baseUrl}}"> + <i class="bi bi-water brand-wordmark-icon" aria-hidden="true"></i> + <span class="brand-wordmark-text">Green</span> +</a> +``` + +The classes it introduces are styled by a small block appended to `main.css`, alongside the custom listing and overview treatments. + +### Starting from it + +A working copy of this theme sits at [`samples/layouts/green-theme/`](https://github.com/wso2/api-platform/tree/main/portals/api-portal/samples/layouts/green-theme) in the API Platform repository, and at the same path in the portal distribution. Copy it, change the seeds, and delete any override you don't want: + +```bash +cp -r samples/layouts/green-theme/ my-theme/ +``` + +## Build and preview + +[Design Mode](design-mode.md) is the fastest way to iterate: it serves the portal from a theme directory on disk with sample data, no database or identity provider needed, and picks up file edits on reload. + +Point it at your theme directory: + +```toml +[api_portal.design_mode] +enabled = true +path_to_layout = "./my-theme/" +``` + +To preview the example above before editing it, point `path_to_layout` at `./samples/layouts/green-theme/`. + +## Package the theme + +The ZIP must contain **one wrapper directory** holding the theme, not the theme's folders at the root. Zip the directory from its parent: + +```bash +zip -r my-theme.zip my-theme/ +``` + +That produces `my-theme/styles/main.css`, `my-theme/layout/main.hbs`, and so on. + +!!! warning "A wrapper directory is required" + The portal classifies each file by the path *below* the first segment. Zipping from inside the theme directory—so that `layout/` and `styles/` sit at the ZIP root—makes `layout/main.hbs` register as a generic template rather than the page shell, and your layout silently won't apply. + +Uploads are size-limited; see `uploads.max_bytes` in [Configurations](../references/configurations.md#uploads). + +## Apply it + +An admin uploads the ZIP to a view under **Settings** → **Theming**. Applying a theme replaces that view's existing theme files entirely rather than merging with them. See [Apply a Theme](theming.md) for the panel, and for downloading the current theme or resetting to the default. + +## Theming vs. API content + +A theme applies to every page in a view. To change one API's overview body—or set its icon, or attach documents—you upload content against that API instead, which leaves the rest of the view alone. See [Customize an API's Content](api-content.md). + +## Related + +- [Apply a Theme](theming.md): upload, download, and reset a view's theme +- [Design Mode](design-mode.md): preview a theme offline against sample data +- [Manage Views](manage-views.md): why themes are per-view +- [Customize an API's Content](api-content.md): change one API's overview body instead of the whole view +- [Configurations](../references/configurations.md#uploads): upload size limits diff --git a/en/docs/api-portal/next/admin-settings/webhook-integration.md b/en/docs/api-portal/next/admin-settings/webhook-integration.md new file mode 100644 index 000000000..9946dbc11 --- /dev/null +++ b/en/docs/api-portal/next/admin-settings/webhook-integration.md @@ -0,0 +1,75 @@ +--- +title: "Configure webhooks in the API Portal & MCP Hub" +description: "Register endpoints to receive signed, real-time notifications when applications, API keys, or subscriptions change." +canonical_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/webhook-integration/ +md_url: https://wso2.com/api-platform/docs/api-portal/admin-settings/webhook-integration.md +tags: + - cloud + - api-portal + - webhooks +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# Webhooks + +The API Portal & MCP Hub doesn't talk to a gateway directly. Instead it publishes a signed HTTP POST to every endpoint you register whenever an application, API key, or subscription changes. Each delivery is signed when the subscriber has a secret; without one it arrives unsigned. Whatever is behind that endpoint decides what to do next. Most deployments register the Platform API control plane, which persists the change and propagates it to the gateways the API is deployed to, but a gateway or a handler of your own can subscribe just as well. + +Enforcement is not immediate, and delivery alone doesn't guarantee it. It happens only once the subscriber accepts the event and acts on it. A timeout or a `non-2xx` response is terminal—there's no retry—so the change stays unenforced, and any queueing on the subscriber's side adds delay. + +This page covers registering a subscriber. For the payload of every event, the delivery envelope, and how to verify and decrypt one, see the [Webhook Event Catalog](../references/webhook-event-catalog.md). + +## Add a webhook + +1. Go to **Settings** and select **Webhooks** under **INTEGRATIONS**. +2. Click **+ Add webhook**. +3. Fill in the fields: + + - **Display name**—required. The name shown in the Webhooks table. + - **Target URL**—required. The endpoint that receives the POSTs. + - **Secret**—required by this form. Used both to sign each delivery and to derive the key that encrypts credential fields. Never shown again after saving; leave it blank when editing to keep the existing value. + - **Timeout (ms)**—how long to wait for a response before giving up. Defaults to 5000. + - **Events**—**All events**, or **Select events** to pick an explicit allowlist. The picker groups them as Subscriptions, API keys, and Applications. + - **Enabled**—turn a subscriber off without deleting it. + +4. Click **Add webhook**. + +!!! warning "Always set a secret" + The secret does double duty: it signs each delivery and derives the key that encrypts credential fields. This form requires one, but the [Management API](../rest-api/webhook-subscribers.md) does not—only `displayName` and `targetUrl` are mandatory there, so a subscriber created programmatically can end up without a secret. + + Such a subscriber still receives events, with two consequences. Deliveries arrive unsigned, so you can't verify they came from the portal. And the four events that carry a credential arrive **without it**—the field is dropped rather than sent in plaintext. Set a secret before relying on `apikey.generated`, `apikey.regenerated`, `subscription.created`, or `subscription.token_regenerated`. + +## Edit or delete a webhook + +Click a webhook's display name, or the pencil icon, to edit it. Click the trash icon to delete it—that can't be undone. + +## What gets delivered + +The portal publishes twelve event types across three groups: + +| Group | Events | +|---|---| +| Subscriptions | `created`, `updated`, `plan_changed`, `token_regenerated`, `deleted` | +| API keys | `generated`, `regenerated`, `revoked`, `application_updated` | +| Applications | `created`, `updated`, `deleted` | + +Four of them carry a credential, encrypted with a key derived from your secret: `apikey.generated`, `apikey.regenerated`, `subscription.created`, and `subscription.token_regenerated`. + +Each delivery carries an `X-Api-Portal-Signature` header and, where relevant, an `encrypted_fields` list naming the fields in `data` that hold an encrypted envelope. The [Webhook Event Catalog](../references/webhook-event-catalog.md) has the full payload for each event, the signature algorithm, and the decryption steps. + +## Delivery behavior + +Two things to design your endpoint around: + +- **A delivery is attempted once.** Any `non-2xx` response, connection error, or timeout is terminal—there's no automatic retry. Make the endpoint reliable, and answer within the configured timeout. +- **Acknowledge fast, work later.** Return a `2xx` and do the propagation asynchronously, rather than holding the connection open while you call a gateway. + +You can read delivery history, including failures and their HTTP status, through the Management API—see [Webhook Events](../rest-api/webhook-events.md). + +## Related + +- [Webhook Event Catalog](../references/webhook-event-catalog.md): every event's payload, headers, signing, and encryption +- [Webhook Subscribers](../rest-api/webhook-subscribers.md): manage subscribers through the Management API +- [Webhook Events](../rest-api/webhook-events.md): read delivery history through the Management API +- [Manage API Keys](../consume-an-api/manage-api-keys.md): the lifecycle behind the `apikey.*` events diff --git a/en/docs/api-portal/next/ai-agent-discovery.md b/en/docs/api-portal/next/ai-agent-discovery.md new file mode 100644 index 000000000..6d5d97109 --- /dev/null +++ b/en/docs/api-portal/next/ai-agent-discovery.md @@ -0,0 +1,132 @@ +--- +title: "AI agent API discovery" +description: "Expose published APIs, MCP servers, and workflows through llms.txt and machine-readable Markdown endpoints so AI agents can discover and invoke them." +canonical_url: https://wso2.com/api-platform/docs/api-portal/ai-agent-discovery/ +md_url: https://wso2.com/api-platform/docs/api-portal/ai-agent-discovery.md +tags: + - cloud + - api-portal + - ai-discovery +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "concept" +--- + +# AI agent API discovery + +The API Portal & MCP Hub has built-in support for AI agent discoverability. Every published API, MCP server, and API workflow is exposed through a set of machine-readable endpoints that AI agents, large language model (LLM)-powered assistants, and agentic frameworks can use to discover, understand, and invoke them without human assistance. + +This page explains what those endpoints return and how agents navigate the portal. + +Every endpoint below is scoped to an organization handle (`{orgName}`) and a [view](admin-settings/manage-views.md) (`{viewName}`, which is `default` unless an admin has created more views). Responses are plain text or JSON, so agents can fetch them without authentication, JavaScript rendering, or a browser. Only discovery is unauthenticated—invoking an API or MCP server still requires its credentials, a subscription where one applies, and the scopes it declares. + +## `llms.txt`: The entry point for agents + +The portal generates an `llms.txt` file on every request—a Markdown index designed as the entry point for AI agents. It gives a structured overview of everything the portal exposes for AI consumption. + +**Endpoint:** + +```text +GET /api-portal/{orgName}/views/{viewName}/llms.txt +``` + +The file opens with the portal's name and description, both configured through [LLM Instructions](admin-settings/llm-instructions.md). It then lists every agent-visible artifact, grouped into the following sections, with each entry linking to that artifact's own Markdown document: + +| Section | Contains | +|---|---| +| API Workflows | Published, agent-visible workflows, each linking to `/api-workflows/{handle}.md` | +| APIs | REST APIs, each linking to `/api/{apiHandle}.md` | +| MCPs | MCP servers, each linking to `/mcp/{apiHandle}.md` | +| GraphQL APIs | GraphQL APIs, each linking to `/api/{apiHandle}.md` | +| Async / WebSocket APIs | WebSocket APIs, each linking to `/api/{apiHandle}.md` | +| WebSub APIs | WebSub APIs, each linking to `/api/{apiHandle}.md` | + +A section is omitted when it holds no agent-visible artifacts. An agent that starts at `llms.txt` therefore learns the full scope of the catalog without crawling the portal, which makes it the standard starting point for LLM-native API consumption. + +!!! tip + Portal admins set the name and description that head `llms.txt` under [LLM Instructions](admin-settings/llm-instructions.md). The same page carries the toggle that turns AI discoverability on or off for the whole portal. + +## Machine-readable endpoints + +Beyond `llms.txt`, the portal serves its catalog, documentation, and specifications as Markdown and raw specification files. + +### API and MCP server catalogs + +| Endpoint | Description | +|---|---| +| `/api-portal/{orgName}/views/{viewName}/apis.md` | Every agent-visible API, grouped by type, as a single Markdown document | +| `/api-portal/{orgName}/views/{viewName}/mcps.md` | Every agent-visible MCP server as a single Markdown document | + +### Per-API and per-MCP-server documentation + +| Endpoint | Description | +|---|---| +| `/api-portal/{orgName}/views/{viewName}/api/{apiHandle}.md` | Full documentation for one API, in Markdown | +| `/api-portal/{orgName}/views/{viewName}/mcp/{apiHandle}.md` | Full documentation for one MCP server, in Markdown | +| `/api-portal/{orgName}/views/{viewName}/api/{apiHandle}/docs/{docType}/{docName}.md` | One attached document, as the raw Markdown the publisher uploaded | +| `/api-portal/{orgName}/views/{viewName}/mcp/{apiHandle}/docs/{docType}/{docName}.md` | The same, for an MCP server | + +A per-API Markdown document is self-contained. It carries: + +- The name, version, description, type, production and sandbox endpoints, tags, and labels +- A subscription plan table listing each plan's rate limits +- Step-by-step authentication instructions for the API's security scheme: OAuth2, API key, or a statement that the API needs no credentials +- Links to every attached document, pointing at the raw Markdown endpoints above +- The full API specification, inlined in a fenced code block, with the live endpoint URLs substituted in + +An agent that fetches this one document usually has everything it needs to make a call. + +### API specifications + +The specification format follows the API type, so each API serves exactly one extension. Requesting any other extension returns `404`. + +| API type | Endpoint | Format | +|---|---|---| +| REST | `/api-portal/{orgName}/views/{viewName}/api/{apiHandle}/docs/specification.json` | OpenAPI (JSON) | +| WebSocket, WebSub | `/api-portal/{orgName}/views/{viewName}/api/{apiHandle}/docs/specification.json` | AsyncAPI (JSON) | +| GraphQL | `/api-portal/{orgName}/views/{viewName}/api/{apiHandle}/docs/specification.graphql` | GraphQL schema (SDL) | +| SOAP | `/api-portal/{orgName}/views/{viewName}/api/{apiHandle}/docs/specification.xml` | WSDL (XML) | +| MCP server | `/api-portal/{orgName}/views/{viewName}/mcp/{apiHandle}/docs/specification.json` | Tool, resource, and prompt schema (JSON) | + +For REST, WebSocket, and WebSub APIs, the portal substitutes the API's live production and sandbox URLs into the specification it serves, so an agent doesn't have to resolve placeholder server entries. + +### API workflows + +| Endpoint | Description | +|---|---| +| `/api-portal/{orgName}/views/{viewName}/api-workflows.md` | Every published, agent-visible workflow as a single Markdown document | +| `/api-portal/{orgName}/views/{viewName}/api-workflows/{handle}.md` | One workflow rendered as Markdown, including its steps and links to the APIs it calls | +| `/api-portal/{orgName}/views/{viewName}/api-workflows/{handle}/arazzo.json` | The raw [Arazzo](https://spec.openapis.org/arazzo/latest.html) specification for one workflow | +| `/api-portal/{orgName}/views/{viewName}/api-workflows/{handle}/prompt` | A JSON object holding the workflow's agent prompt, description, raw content, and source APIs | + +The `arazzo.json` endpoint returns `404` for a non-Arazzo workflow. + +## How agents navigate the portal + +A typical agent discovery flow looks like this: + +1. **Start at `llms.txt`.** The agent fetches the portal's `llms.txt` for an overview of the available APIs, MCP servers, and workflows. +2. **Browse a catalog.** If the index isn't detailed enough, the agent fetches `apis.md` or `mcps.md` to read every description at once. +3. **Retrieve per-artifact documentation.** Once the agent identifies a relevant API, it fetches `/api/{apiHandle}.md` for the endpoints, authentication steps, plans, and inlined specification. +4. **Read an attached document.** For prose the specification can't express—a getting-started guide, an authentication walkthrough, known limitations—the agent follows the document links in that Markdown file. +5. **Fetch the specification separately.** When the agent needs the specification as a parseable file rather than as inlined text, it retrieves the `specification.*` endpoint for the API's type. +6. **Follow a workflow.** If a published workflow matches the task, the agent retrieves the Arazzo specification and agent prompt, then follows a vetted, step-by-step call sequence instead of reasoning from scratch. + +The **Try with AI** button on an [API's overview page](discover-apis/browse-apis.md#open-an-api) hands this flow to an agent directly. It produces a prompt that points the agent at that API's `.md` URL and asks it to summarize the API before doing anything else. + +## Visibility controls + +Three separate controls decide what agents see: + +- **Per-artifact agent visibility.** All published APIs, MCP servers, and workflows are agent-visible by default. Setting an artifact to hidden removes it from `llms.txt`, the catalogs, and every Markdown and specification endpoint, while leaving it visible to human users in the portal. For APIs, see [Make an API AI-Ready](../../cloud/develop-api-proxy/make-api-ai-ready.md). For workflows, see [Managing API Workflows](admin-settings/manage-api-workflows.md). +- **Portal-wide AI discoverability.** Turning off **Portal is AI-discoverable** under [LLM Instructions](admin-settings/llm-instructions.md) makes every endpoint on this page return `404`, including `llms.txt` itself. +- **Served artifact types.** A deployment that doesn't serve a given artifact type returns `404` for that type's catalog, Markdown, and specification endpoints. See [Artifact types](setting-up/artifact-types.md). + +## Related + +- [LLM Instructions](admin-settings/llm-instructions.md): set the portal name and description at the top of `llms.txt`, and toggle AI discoverability +- [Managing API Workflows](admin-settings/manage-api-workflows.md): publish workflows that guide agents through common multi-step use cases +- [API Workflows](api-workflows.md): how agents discover and follow published workflows +- [Make an API AI-Ready](../../cloud/develop-api-proxy/make-api-ai-ready.md): publisher guidance on descriptions, specifications, and visibility settings +- [MCP Servers](mcp-servers/overview.md): how MCP servers are published, discovered, and connected to +- [MCP Registry API](mcp-registry.md): discover the same servers in the Model Context Protocol registry format diff --git a/en/docs/api-portal/next/api-workflows.md b/en/docs/api-portal/next/api-workflows.md new file mode 100644 index 000000000..01120762e --- /dev/null +++ b/en/docs/api-portal/next/api-workflows.md @@ -0,0 +1,110 @@ +--- +title: "API workflows in the API Portal & MCP Hub" +description: "Find a published API workflow, read its steps in the portal, and hand it to an AI agent through the generated agent prompt or the Arazzo specification." +canonical_url: https://wso2.com/api-platform/docs/api-portal/api-workflows/ +md_url: https://wso2.com/api-platform/docs/api-portal/api-workflows.md +tags: + - cloud + - api-portal + - api-workflows +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# API workflows + +An API workflow is a published, multi-step sequence of API calls that solves one use case end to end—validate a patient, then book a specialist, then confirm the appointment. An admin authors the workflow once, and everyone who needs that sequence follows the same vetted path instead of piecing it together from individual specifications. + +Workflows serve two audiences from the same definition. You read one in the portal to understand the calls and their order. An AI agent reads the machine-readable version and executes it, which is the point: left to reason freely, agents misorder calls, skip required steps, and invent endpoints. + +## Browse the workflow gallery + +Click **API Workflows** in the sidebar. The gallery shows a card for every published workflow, and you don't need to sign in to see it. + +Each card carries: + +- The workflow's display name and description +- A **POWERED BY** row of pills naming the APIs the workflow calls, up to four, then a `+N more` pill +- An **AI Ready** badge when AI agents can discover the workflow + +Click a card to open the workflow. + +!!! note + The gallery lists only workflows an admin has published; drafts appear nowhere. Admins also get a button into the authoring page—**Manage workflows** in the header, or **Create workflow** when the gallery is empty. See [Managing API Workflows](admin-settings/manage-api-workflows.md). + +## Read a workflow + +The detail page opens with the workflow's name and description, then renders the definition in one of two ways, depending on how the admin authored it: + +- **Arazzo workflows** render in an interactive [Arazzo](https://spec.openapis.org/arazzo/latest.html) viewer, in a split layout: a flow diagram of the steps alongside the specification behind them. Use the diagram to follow the call order, and the specification pane for each step's inputs, outputs, and success criteria. +- **Markdown workflows** render as formatted prose, exactly as the admin wrote them. + +An Arazzo workflow opens with its description above the interactive viewer: + +![Workflow detail page with a description, version and Arazzo badges, and a flow diagram of the workflow steps](../../assets/img/standalone-devportal/api-workflows/workflow-overview.png) + +An Arazzo workflow names its source APIs in `sourceDescriptions`. Those are the APIs you'll be calling, and each one's own documentation gives you the base URL, the operations, and the authentication it expects. To fetch the raw specification for tooling that reads Arazzo, request it directly: + +```text +GET /api-portal/{orgName}/views/{viewName}/api-workflows/{handle}/arazzo.json +``` + +## Hand a workflow to an AI agent + +Click **Try with AI** on the detail page. The portal generates an agent prompt built around the workflow's own definition and shows it in a modal, where you can: + +- **Copy** the prompt to paste into any assistant +- **Download** it as a `.txt` file +- **Run in Claude** to open it in a new Claude conversation + +The **Try with AI** button appears only when the workflow is agent-visible. + +### What the prompt contains + +The generated prompt has two sections, and it opens by telling the agent which one applies: + +| Section | For | What it instructs | +|---|---|---| +| **API Execution Agent** | A browser-based assistant that will call the APIs itself | Read the workflow definition, execute the steps in order, track state between them, collect credentials from you, retry 5xx responses up to three times, and stop on a 4xx | +| **App Builder Agent** | A coding or IDE agent building an application | Read the workflow, then the portal's `llms.txt`, then each API's Markdown and OpenAPI specification. Map every step to a real endpoint and confirm with you before writing code, then build one service per API behind a single orchestrator | + +Both sections point at the workflow's own `.md` URL as the source of truth, so the agent works from the live definition rather than from a snapshot pasted into the prompt. + +## How agents discover workflows on their own + +An agent that hasn't been handed a prompt finds workflows through the portal's machine-readable endpoints. All of them are scoped to an organization and a view, and none require authentication. + +| Endpoint | Returns | +|---|---| +| `/api-portal/{orgName}/views/{viewName}/llms.txt` | The portal index. Its **API Workflows** section lists every agent-visible workflow with its description, each linking to the workflow's own Markdown | +| `/api-portal/{orgName}/views/{viewName}/api-workflows.md` | A Markdown list of every agent-visible workflow, each linking to `/api-portal/{orgName}/views/{viewName}/api-workflows/{handle}.md` | +| `/api-portal/{orgName}/views/{viewName}/api-workflows/{handle}.md` | One workflow in Markdown. For an Arazzo workflow: status, description, source APIs linked to their documentation, guidance on each source's authentication, and the full Arazzo specification inlined. For a Markdown workflow: the content the admin authored, as written | +| `/api-portal/{orgName}/views/{viewName}/api-workflows/{handle}/arazzo.json` | The raw Arazzo specification. Returns `404` for a Markdown-authored workflow | +| `/api-portal/{orgName}/views/{viewName}/api-workflows/{handle}/prompt` | A JSON object with the workflow's agent prompt, description, raw content, and source APIs | + +A typical agent flow is: read `llms.txt` or `api-workflows.md` to see what exists, fetch `{handle}.md` for the workflow that matches the task, follow its links to each source API's documentation for endpoints and security schemes, then execute the steps in order, passing outputs between them. + +When a workflow's source URL points at an API published in this same portal, the portal rewrites that link to the API's Markdown documentation, so an agent stays inside the machine-readable surface instead of being sent to a rendered page. + +For the portal's full set of agent-facing endpoints, see [AI Agent Discovery](ai-agent-discovery.md). + +## Workflow visibility + +Two independent settings decide who sees a workflow: + +| Setting | Values | Effect | +|---|---|---| +| Status | Draft, Published | Draft workflows appear nowhere. Publishing puts a workflow in the gallery for the view it belongs to | +| Agent visibility | Visible, Hidden | A hidden workflow stays in the gallery for people, but drops out of `llms.txt`, `api-workflows.md`, and every per-workflow endpoint, and loses its **Try with AI** button | + +Hiding a workflow from agents is how you publish it for human developers while it's still being validated for automated execution. Admins set both, per workflow—see [Managing API Workflows](admin-settings/manage-api-workflows.md). + +Turning off **Portal is AI-discoverable** under [LLM Instructions](admin-settings/llm-instructions.md) makes every machine-readable workflow endpoint return `404`, whatever each workflow's own setting says. + +## Related + +- [Managing API Workflows](admin-settings/manage-api-workflows.md): author, publish, and set the visibility of workflows +- [AI Agent Discovery](ai-agent-discovery.md): the portal's full set of agent-facing endpoints +- [Consume an API](consume-an-api/overview.md): the credentials you'll need for the APIs a workflow calls +- [Artifact types](setting-up/artifact-types.md): whether this deployment serves API workflows at all diff --git a/en/docs/api-portal/next/concepts.md b/en/docs/api-portal/next/concepts.md new file mode 100644 index 000000000..4af670bf3 --- /dev/null +++ b/en/docs/api-portal/next/concepts.md @@ -0,0 +1,119 @@ +--- +title: "API Portal concepts" +description: "The key building blocks of the API Portal & MCP Hub—organization, views, labels, APIs, MCP servers, API workflows, subscription plans, applications, subscriptions, API keys, and more—and how they relate to each other." +canonical_url: https://wso2.com/api-platform/docs/api-portal/concepts/ +md_url: https://wso2.com/api-platform/docs/api-portal/concepts.md +tags: + - cloud + - api-portal + - concepts +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "concept" +--- + +# Concepts + +This page explains the key building blocks of the API Portal & MCP Hub and how they relate to each other. + +## Organization + +Each portal instance serves exactly **one organization**—the top-level container for the portal's APIs, MCP servers, applications, subscriptions, and users. The organization is pinned by `handle` in the `[api_portal.organization]` table of `config.toml`, and is seeded automatically on first startup. The portal refuses to start without a handle. + +The database schema itself is multi-organization—one shared database can hold several organizations, each served by its own portal instance—but any single instance is bound to its configured organization, and a request scoped to a different organization is rejected. + +The handle appears in every portal URL: + +``` +https://<host>/api-portal/<orgHandle>/views/<viewName> +``` + +In local-auth mode it must match the organization ID the Platform API asserts in the `org_handle` claim. In IDP mode the token's organization claim has to resolve to this same handle. See [Connect an identity provider](setting-up/authentication/connect-an-identity-provider.md#step-5-make-the-organization-claim-resolve-to-your-organization). + +## View + +A **view** is a filtered, branded subset of the organization's APIs and Model Context Protocol (MCP) servers—for example, one view for internal developers and one for external partners. Each view shows only the artifacts carrying its assigned labels. It can also have its own layout, large language model (LLM) instructions, and API workflows. + +Each view has its own URL: + +``` +https://<host>/api-portal/<orgHandle>/views/<viewName> +``` + +See [Manage Views](admin-settings/manage-views.md). + +## Label + +A **label** is a tag you assign to an API or MCP server to control which views expose it. A view shows only the artifacts carrying at least one of its assigned labels—so an artifact labelled `internal` appears only in views that include the `internal` label. Labels differ from an artifact's **tags**, which are informational and don't affect visibility. See [Manage Labels](admin-settings/manage-labels.md). + +## Layout + +A **layout** is a custom Handlebars template that defines the structure of a view's pages. It's one part of a **theme**—a partial copy of the portal's default template tree, holding only the styles, layouts, partials, and page templates you want to change. Anything a theme omits is served from the default. See [Theming](admin-settings/theming.md), and [Design Mode](admin-settings/design-mode.md) for building one offline. + +## Artifact types + +The portal serves three kinds of artifact: **APIs**, **MCP servers**, and **API workflows**. Which of them a given portal exposes is an operator setting—the `enabled_types` allowlist in the `[api_portal.artifacts]` config. A type that isn't enabled gets no navigation entry and no landing-page section, and its routes return `404`. See [Artifact types](setting-up/artifact-types.md). + +## API + +An **API** is an entry in the portal catalog that developers can discover and subscribe to. The portal supports these API types: + +| Type | Definition format | +|---|---| +| REST | OpenAPI (Swagger) YAML/JSON | +| GraphQL | GraphQL schema (SDL) | +| SOAP | WSDL/XML | +| WebSocket | AsyncAPI YAML/JSON | +| WebSub | AsyncAPI YAML/JSON | + +Each API can have its own landing page content, documentation sections, icon, and banner image. APIs carry labels (which control view visibility) and tags (informational), and can be marked agent-visible so AI agents discover them. See [Manage APIs](admin-settings/manage-apis.md). + +## MCP server + +An **MCP server** is a Model Context Protocol server published in the portal alongside your APIs—the "MCP Hub" half of the portal. Its contract is its **definition**—the tools, resources, and prompts it exposes—rather than an OpenAPI-style specification. MCP servers are managed and subscribed to the same way APIs are, and every one of them is agent-discoverable. See [MCP Servers](mcp-servers/overview.md) and, for the admin side, [Manage MCP Servers](admin-settings/manage-mcp-servers.md). + +## API workflow + +An **API workflow** is a published, multi-step sequence of API calls defined in [Arazzo format](https://spec.openapis.org/arazzo/latest.html). Workflows are authored by admins and published per view for both human developers and AI agents to discover and follow. See [Managing API Workflows](admin-settings/manage-api-workflows.md) and [API Workflows](api-workflows.md). + +## Subscription plan + +A **subscription plan** is a named usage tier that controls how much of an API or MCP server a developer can consume. Plans are attached to an artifact during publishing, and developers choose a plan when subscribing. + +Plans can define rate limits—a request count (or event count, for async APIs) per time window (minute, hour, day, or month). When `organization.auto_create_subscription_plans` is on, a default set of plans (Bronze, Silver, Gold, Unlimited, AsyncUnlimited) is created with the organization. See [Subscription Plans](admin-settings/subscription-plans.md). + +## Application + +An **application** is a logical container—representing a mobile app, web app, device, or script—that a developer creates in the portal. For OAuth2-secured APIs, an application holds the client ID(s) that link to OAuth applications created directly in a key manager; the portal never generates or stores consumer key/secret pairs. + +A developer can have multiple applications, each with independent OAuth2 client IDs. See [Manage Applications](consume-an-api/manage-applications.md). + +!!! note + Applications are not required for subscriptions or API key generation. Subscriptions are made directly to an API or MCP server, and API keys are bound to an API or MCP server—not to an application. + +## Subscription + +A **subscription** is a developer's access grant to a specific API or MCP server under a chosen subscription plan. The plan determines the developer's rate limits and quota for that artifact. + +Subscriptions are made directly to the artifact—no application is involved. Once subscribed, the developer can invoke it under the terms of the chosen plan. See [Manage Subscriptions](consume-an-api/manage-subscriptions.md). + +## API key + +An **API key** is a simple token bound to a specific API or MCP server, used to authenticate requests to artifacts that use API key-based authentication. API keys are generated per artifact—not per application or per subscription. + +API keys can be generated, regenerated (rotated), or revoked. Each of those publishes a webhook event, so a handler in front of your API Gateway can enforce the change once it receives the event—see the [Webhook Event Catalog](references/webhook-event-catalog.md). See [Manage API Keys](consume-an-api/manage-api-keys.md). + +!!! note + The portal generates keys through the UI for REST, WebSocket, and WebSub APIs whose definition declares API-key security. Keys for GraphQL and SOAP APIs exist solely through the [API Keys](rest-api/api-keys.md) Management API. Keys for MCP servers exist only through the [MCP Server Keys](rest-api/mcp-server-keys.md) Management API. + +## OAuth2 credentials + +For APIs that use OAuth2, developers create an OAuth application directly in a [key manager](admin-settings/key-manager-integration.md), then link the resulting **client ID** to an application in the portal. The portal never sees or stores the client secret—it's supplied by the developer each time they generate an access token, and the portal proxies that token request to the key manager. See [Consume an API Secured with OAuth2](consume-an-api/oauth2.md). + +## Key manager + +A **key manager** is an external OAuth2 authorization server configured for the organization. Developers create and own their OAuth applications there; the portal only stores a reference to the client ID and proxies `client_credentials` token requests to the key manager's token endpoint. You can configure one or more key managers for the organization. See [Key Manager Integration](admin-settings/key-manager-integration.md). + +## Webhook subscriber + +A **webhook subscriber** is an HTTPS endpoint you register to receive real-time event notifications from the portal. When an application, API key, or subscription changes, the portal fires a signed HTTP POST to every matching subscriber; what the subscriber does with it—for example, propagating the change to an API Gateway—is up to whatever you run behind that endpoint. See [Webhook Integration](admin-settings/webhook-integration.md) to register one, and the [Webhook Event Catalog](references/webhook-event-catalog.md) for the twelve events and their payloads. diff --git a/en/docs/api-portal/next/consume-an-api/api-key.md b/en/docs/api-portal/next/consume-an-api/api-key.md new file mode 100644 index 000000000..c0bf85df1 --- /dev/null +++ b/en/docs/api-portal/next/consume-an-api/api-key.md @@ -0,0 +1,64 @@ +--- +title: "Consume an API secured with an API key" +description: "Send a generated API key in the header an API's specification declares, and combine it with a subscription token when the API requires one." +canonical_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/api-key/ +md_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/api-key.md +tags: + - cloud + - api-portal + - authentication +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# Consume an API secured with an API key + +An API key is bound to one API or Model Context Protocol (MCP) server. You generate it from that artifact's own **API Keys** page, and it authenticates your requests to that artifact only. No key manager is involved, and an application is optional—you can associate a key with one for usage analytics, which changes nothing about how the key works. + +## Prerequisites + +- The API declares API key security. Look for a `securitySchemes` entry of type `apiKey` in the API's specification. +- You have a key. See [Manage API Keys](manage-api-keys.md) for the generate, rotate, and revoke lifecycle. + + !!! note + The portal generates keys for REST, WebSocket, and WebSub APIs whose specification declares API key security. GraphQL, SOAP, and MCP artifacts get no **API Keys** button even when their specification declares an `apiKey` scheme—for those, obtain the key from whoever operates the API and send it the same way. + +- If the API has subscription plans, [subscribe to one](manage-subscriptions.md). The subscription is a separate credential from the key, and you send both. + +## Invoke the API + +Send the key in the header the API's specification names. The portal's API Keys page describes a key as usable "as a bearer token or `apikey` header", and `apikey` is the default the portal assumes: + +```bash +curl -X GET "https://api.example.com/orders/v1/orders" \ + -H "apikey: <YOUR_API_KEY>" +``` + +Replace `<YOUR_API_KEY>` with the key you copied when generating it, and the URL with the API's production or sandbox endpoint from its overview page. + +!!! important + The header name comes from the API, not from the portal. Read the `name` field of the `apiKey` scheme in the API's specification and use exactly that. Some APIs expect `apikey`, some `api-key`, some a name of their own. + +### When the API also requires a subscription + +Send the subscription token alongside the key: + +```bash +curl -X GET "https://api.example.com/orders/v1/orders" \ + -H "apikey: <YOUR_API_KEY>" \ + -H "Subscription-Key: <YOUR_SUBSCRIPTION_TOKEN>" +``` + +The two answer different questions: the key says who you are, and the subscription token says which plan's rate limit applies to you. The subscription header name also comes from the specification—`Subscription-Key` is the common case. + +## Check it before writing code + +Open the API's [documentation page](../discover-apis/browse-apis.md#read-the-specification-and-try-it) and use the **Try It** console on a REST API, or **Tryout** on a GraphQL API, which has a dedicated **API Key** tab with fields for the header name and value. A call that succeeds there confirms both the header names and the key itself. + +## Related + +- [Consume an API](overview.md): which credentials an API expects, and how to tell +- [Manage API Keys](manage-api-keys.md): generate, rotate, revoke, and associate keys with an application +- [Manage Subscriptions](manage-subscriptions.md): get a subscription token +- [Consume an API Secured with OAuth2](oauth2.md): the alternative for OAuth2-secured APIs diff --git a/en/docs/api-portal/next/consume-an-api/manage-api-keys.md b/en/docs/api-portal/next/consume-an-api/manage-api-keys.md new file mode 100644 index 000000000..303adc74f --- /dev/null +++ b/en/docs/api-portal/next/consume-an-api/manage-api-keys.md @@ -0,0 +1,98 @@ +--- +title: "Manage API keys" +description: "Generate, rotate, revoke, and associate API keys for a subscribed API in the API Portal & MCP Hub." +canonical_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/manage-api-keys/ +md_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/manage-api-keys.md +tags: + - cloud + - api-portal + - api-keys +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "how-to" +--- + +# Manage API keys + +API keys are bound to a specific API. You generate a key directly for an API, and that key authenticates your requests to it—no application is required. + +## Prerequisites + +The API must have API key authentication enabled—check the API's documentation or the security section of its specification to confirm. If the API requires a subscription, [subscribe to it](manage-subscriptions.md) first. + +## Generate an API key + +1. Sign in to the API Portal & MCP Hub. +2. Click **APIs** from the sidebar and open the API you want to invoke. +3. In the API's submenu, click **API Keys**. +4. Click **Generate API key**. +5. In the **Generate API key** dialog, enter a **Name** for the key (e.g. `my-prod-key`) and optionally set an **Expires at** date. +6. Click **Generate** and wait for the key to be created. +7. **Copy the API key immediately.** The key won't be visible in the UI after you close this dialog. +8. Click **Done**. + +Once you have a key, see [Consume an API Secured with an API Key](api-key.md) for how to use it. + +## Rotate an API key + +If a key is compromised, or you want to rotate it as a security practice: + +1. Navigate to the API's **API Keys** page. +2. Click **Regenerate** next to the key. +3. Adjust the **Name** or **Expires at** in the dialog if needed, then confirm. The old key is immediately invalidated. +4. Copy the new key from the dialog. + +!!! warning + Update all services using the old key before or immediately after regenerating. The old key stops working as soon as regeneration is complete. + +## Revoke an API key + +To permanently invalidate a key: + +1. Navigate to the API's **API Keys** page. +2. Click **Revoke** next to the key. +3. Confirm the revocation. + +Revoked keys can't be recovered. Generate a new key if you need access again. + +## What the API keys page shows + +The page lists every key for the API, with its name, status, expiry (or **Never**), and the application it's attributed to. Above the table, a dropdown filters the list by application. + +Each row carries three actions: **Associate app** (or **Change app** when one is already set), **Regenerate**, and **Revoke**. + +## Associate an API key with an application + +API keys are always generated for an API directly—never for an application. Associating a key with an application afterward is optional. It exists purely for **usage analytics attribution**. In reporting, it groups that key's request metrics under the application. It has no effect on the key's validity or authorization, and a key works identically whether it is associated with an application. + +You can do this from either side. From the key's own row, click **Associate app** or **Change app**. Or, from the application: + +1. Sign in to the API Portal and open **Applications** in the sidebar. +2. Select the application you want to attribute usage to. +3. Go to the application's **API Keys** tab. +4. Click **Associate existing key**. +5. In the dialog, select the **API** the key belongs to, then select the specific **Key** from that API's existing keys. +6. Click **Associate**. + +The key now appears in the application's **API Keys** list, alongside the API it belongs to and its status. + +To remove the association later, click **Remove** next to the key in the same list—this only detaches the key from the application; the key itself remains active and usable. + +!!! note + An application can have keys associated from multiple different APIs, and a single API key can be reassociated to a different application at any time by repeating this flow. + +## Key lifecycle events + +When you generate, regenerate, or revoke an API key, the portal publishes a webhook event to every subscriber configured for it. Enforcement happens once that subscriber receives and accepts the event. Delivery is attempted exactly once, so a timeout or a non-2xx response leaves the change unenforced, and any queueing or processing on the subscriber's side adds delay. + +Changing a key's application association publishes `apikey.application_updated`. That's an analytics notification—it doesn't affect whether the key works. + +The key itself travels encrypted on `apikey.generated` and `apikey.regenerated`. For the payload of each event, see the [Webhook Event Catalog](../references/webhook-event-catalog.md); to register a subscriber, see [Webhook Integration](../admin-settings/webhook-integration.md). + +## Related + +- [Manage Subscriptions](manage-subscriptions.md)—subscribe if the API requires a subscription +- [Consume an API Secured with an API Key](api-key.md)—use the key to invoke the API +- [Consume an API Secured with OAuth2](oauth2.md)—alternative for OAuth2-secured APIs +- [Manage Applications](manage-applications.md)—set up an application to associate keys with +- [Webhook Event Catalog](../references/webhook-event-catalog.md)—the `apikey.*` events this lifecycle publishes diff --git a/en/docs/api-portal/next/consume-an-api/manage-applications.md b/en/docs/api-portal/next/consume-an-api/manage-applications.md new file mode 100644 index 000000000..c7d4d23ec --- /dev/null +++ b/en/docs/api-portal/next/consume-an-api/manage-applications.md @@ -0,0 +1,87 @@ +--- +title: "Manage applications in the API Portal & MCP Hub" +description: "Create an application to hold OAuth2 client IDs, edit its name and description, associate API keys for analytics, and delete it." +canonical_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/manage-applications/ +md_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/manage-applications.md +tags: + - cloud + - api-portal + - applications +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# Manage applications + +An application is a logical stand-in for a physical one—a mobile app, web app, device, or CLI tool. In the API Portal & MCP Hub it does one job: it holds the OAuth2 client IDs, created in a key manager, that you use to call OAuth2-secured APIs. + +Applications belong to you. Each developer sees only their own, and you can keep as many as you need—a `MyApp-Production` application and a `MyApp-Staging` application, say, linked to different OAuth applications in the key manager. + +!!! note + An application is **required** for OAuth2-secured APIs, since it's what holds the client ID. It's **optional** everywhere else. [Subscriptions](manage-subscriptions.md) are made directly to an API, and [API keys](manage-api-keys.md) are bound to an API, so neither needs one. You can associate an existing API key with an application for usage analytics, which changes nothing about the key. + +## Create an application + +1. Sign in to the API Portal & MCP Hub. +2. Click **Applications** in the sidebar. +3. Click **Create application**. On an empty **Applications** page the button sits in the middle of the page rather than the header. +4. Enter an **Application name**, up to 100 characters. This is the only required field. +5. Optionally add a **Description**, up to 256 characters. +6. Click **Create**. + +The new application appears in the list. The portal derives a URL handle from the name—lowercased, with spaces and underscores turned into hyphens—and that handle has to be unique in your organization, so creating a second application with the same name fails. + +## Open an application + +Click an application's card. The detail page is one scrolling page with three sections: + +| Section | What it holds | +|---|---| +| Header | The name and description, each editable in place | +| **Manage Keys** | Per-environment OAuth2 credentials, one card per key manager, with **Credentials**, **Generate Token**, and **cURL** tabs | +| **API keys** | Existing API keys associated with this application, for analytics | + +## Edit the name or description + +Both fields edit in place on the detail page: + +1. Click the pencil icon next to the name, or next to the description. When the application has no description yet, click **+ Add description** instead. +2. Type your change. +3. Click the checkmark to save, or the cross to cancel. + +## Link OAuth2 credentials + +The **Manage Keys** section is where you paste a client ID from your key manager and generate access tokens against it. That's the whole reason applications exist—see [Consume an API Secured with OAuth2](oauth2.md) for the full sequence. + +## Associate an API key + +Associating a key with an application attributes its usage in analytics. It has no effect on whether the key works, and no effect on what the application can do. + +1. On the detail page, scroll to **API keys**. +2. Click **Associate existing key**. +3. Choose an **API**, then choose one of your existing keys for that API. +4. Click **Associate**. + +The key appears in the table with its name, API, and status. Click **Remove** on a row to drop the association; the key itself keeps working. See [Manage API Keys](manage-api-keys.md) for generating keys in the first place. + +## Delete an application + +1. Go to **Applications** in the sidebar. +2. Click the trash icon on the application's card. +3. Click **Delete** to confirm. + +Deleting is irreversible, and it's narrower than the confirmation dialog suggests. What it actually does: + +- **Removes the client ID mappings** the portal held for that application. Any of your APIs that relied on tokens from those credentials stop working once the tokens expire. +- **Dissociates any API keys** that were linked to it. The keys themselves survive and keep working—only the analytics association goes. +- **Leaves your subscriptions untouched.** Subscriptions are held against the API, not the application, so they're unaffected either way. +- **Doesn't contact the key manager.** The OAuth application there stays exactly as it was, and access tokens already issued stay valid until they expire. To invalidate those, use the key manager's own console or revocation endpoint. + +## Related + +- [Consume an API Secured with OAuth2](oauth2.md): link a client ID and generate an access token +- [Manage Subscriptions](manage-subscriptions.md): subscriptions are made directly to an API, not through an application +- [Manage API Keys](manage-api-keys.md): generate a key and optionally associate it with an application +- [Key Manager Integration](../admin-settings/key-manager-integration.md): admin guide for connecting key managers +- [Webhook Event Catalog](../references/webhook-event-catalog.md): the `application.*` events, and what deletion does to associated keys diff --git a/en/docs/api-portal/next/consume-an-api/manage-subscriptions.md b/en/docs/api-portal/next/consume-an-api/manage-subscriptions.md new file mode 100644 index 000000000..005d821a3 --- /dev/null +++ b/en/docs/api-portal/next/consume-an-api/manage-subscriptions.md @@ -0,0 +1,91 @@ +--- +title: "Manage subscriptions in the API Portal & MCP Hub" +description: "Subscribe to an API under a plan, use the subscription token it issues, and switch, suspend, or cancel the subscription later." +canonical_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/manage-subscriptions/ +md_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/manage-subscriptions.md +tags: + - cloud + - api-portal + - subscriptions +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# Manage subscriptions + +A subscription grants you access to one API or MCP server under a chosen plan, which sets your rate limits and quota. It also issues a **subscription token**—a credential you send with every call so the gateway knows which plan to enforce. + +Subscriptions belong to you, not to an application. You don't need to create an application first, and the same subscription works from anywhere you call the API. + +## Subscribe to an API + +1. Sign in to the API Portal & MCP Hub. +2. Click **APIs** in the sidebar and open the API you want. +3. Click **Subscribe** in the header, or scroll to the **Subscription plans** panel. +4. Select **Subscribe** for the plan you want. + +The subscription is created the moment you click—there's no confirmation step. A dialog then shows your subscription token. + +!!! warning + Copy the token before closing the dialog. You can retrieve it again later from **Subscriptions** in the sidebar, but nothing on screen tells you that at the time. + +You hold at most one subscription per API. Once you have one, the other plan cards relabel their buttons to **Switch plan**—see [Switch to a different plan](#switch-to-a-different-plan). + +## Use the subscription token + +Send the token in the header the API's specification names, commonly `Subscription-Key`: + +```bash +curl -X GET "https://api.example.com/orders/v1/orders" \ + -H "Subscription-Key: <YOUR_SUBSCRIPTION_TOKEN>" +``` + +The subscription token is not a substitute for authentication. Most APIs also expect an API key or an OAuth2 access token, and you send both headers together. See [Consume an API](overview.md) for which credentials an API expects and how to tell. + +## View and manage a subscription + +Every subscription you hold is listed under **Subscriptions** in the sidebar, in a table showing the API, version, plan, and status. Click the **Manage subscription** button on a row to open the manage dialog. The same dialog opens from **View subscription** on the plan card on the API's overview page. + +From the dialog you can: + +- **Reveal or copy the subscription token.** The token is masked until you click the eye icon. +- **Regenerate** the token. The old token stops working immediately, so update anything using it. You're asked to confirm first. +- **Suspend** the subscription. It goes inactive and the button becomes **Resume**. Use this to stop traffic without giving up the subscription or its token. +- **Unsubscribe**, after a confirmation. + +## Switch to a different plan + +To move to a different plan on an API you're already subscribed to: + +1. Open the API's overview page. +2. Click **Switch plan** on the plan card you want. +3. Confirm in the dialog. + +Your subscription token stays the same, so nothing you've already configured breaks. What changes is the rate limits and quota the new plan sets. + +## Cancel a subscription + +1. Go to **Subscriptions** in the sidebar. +2. Click **Manage subscription** on the row you want to cancel. +3. Click **Unsubscribe**, then confirm. + +Cancelling invalidates the subscription token. Anything calling the API with it stops working. You can subscribe again at any time, and you'll get a new token. + +## APIs without plans + +Not every API has subscription plans. When an API doesn't, its overview page shows no **Subscription plans** panel and no **Subscribe** button, and there's nothing to subscribe to—call it with whatever authentication its specification requires. + +To try an API before wiring it into your code, use the **Try It** console on the [documentation page](../discover-apis/browse-apis.md#read-the-specification-and-try-it) rather than looking for a subscription. + +## Subscription plans + +Plans set how much of an API you can consume. The API publisher decides which plans an API offers, and each plan's rate limit appears on its card. For how admins define them, see [Subscription Plans](../admin-settings/subscription-plans.md). + +## Related + +- [Consume an API](overview.md): which credentials an API expects, and how they combine +- [API Overview](../discover-apis/browse-apis.md#open-an-api): where the subscription plans panel lives +- [Manage API Keys](manage-api-keys.md): generate an API key for a subscribed API +- [Subscription Plans](../admin-settings/subscription-plans.md): admin guide for defining plans +- [Webhook Event Catalog](../references/webhook-event-catalog.md): the `subscription.*` events subscribing, switching, and unsubscribing publish diff --git a/en/docs/api-portal/next/consume-an-api/oauth2.md b/en/docs/api-portal/next/consume-an-api/oauth2.md new file mode 100644 index 000000000..105c4bcb6 --- /dev/null +++ b/en/docs/api-portal/next/consume-an-api/oauth2.md @@ -0,0 +1,112 @@ +--- +title: "Consume an API secured with OAuth2" +description: "Link a key manager client ID to your application, generate an access token from the portal or with curl, and call an OAuth2-secured API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/oauth2/ +md_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/oauth2.md +tags: + - cloud + - api-portal + - authentication +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# Consume an API secured with OAuth2 + +OAuth2-secured APIs expect a bearer token issued by a key manager. The OAuth application that issues it lives in the key manager, not in the portal—the portal stores only its client ID and proxies token requests on your behalf. It asks for the consumer secret each time you generate a token through the UI, uses it for that one request, and doesn't retain it. + +## Prerequisites + +Three things have to exist before you can generate a token: + +1. **An OAuth application in a key manager.** Ask your admin which key managers your organization has connected and how to create an OAuth application in the one you need. For the admin side, see [Key Manager Integration](../admin-settings/key-manager-integration.md). +2. **An application in the portal.** This is the container that holds the client ID. See [Manage Applications](manage-applications.md). +3. **A subscription, if the API has plans.** Subscriptions are made directly to the API, independently of your application. See [Manage Subscriptions](manage-subscriptions.md). + +## Link a client ID + +1. Sign in to the API Portal & MCP Hub. +2. Click **Applications** in the sidebar, then open your application. +3. In the **Manage Keys** section, choose the **Production** or **Sandbox** tab. + + !!! info + The two environments hold separate credentials. Sandbox tokens are for testing and don't work against production endpoints. + +4. Find the card for the key manager you want to use, paste the **client ID** of the OAuth application you created there, and click **Add**. + +The card now shows three tabs: **Credentials**, **Generate Token**, and **cURL**. The **Credentials** tab holds the consumer key (client ID) with a copy button. No secret appears there, because the portal never received one. + +## Generate an access token + +You have two routes, and they produce the same token. + +### From the portal + +1. On the key manager's card, open the **Generate Token** tab. +2. Add scopes under **Request Permissions (Scopes)** if the API needs them. Type each scope and press <kbd>Enter</kbd>. The field starts pre-filled with the scopes your subscriptions grant. +3. Click **Generate access token**. +4. Enter your consumer secret when prompted. The portal uses it once to make the token request and doesn't keep it. +5. Copy the token from the dialog. + + !!! warning + The token is shown once and can't be retrieved afterward. Copy it before closing the dialog. The dialog's **Regenerate** button issues a fresh one if you lose it. + +The dialog also shows **Response Permissions (Scopes)**—the scopes the key manager actually granted, which can be narrower than what you asked for. + +### With curl + +Open the **cURL** tab for a ready-made command with your token endpoint and client ID already filled in, then copy it. It uses the client credentials grant: + +```bash +curl -X POST "https://keymanager.example.com/oauth2/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=client_credentials" \ + -u "<CONSUMER_KEY>:<CONSUMER_SECRET>" +``` + +The response carries the token: + +```json +{ + "access_token": "eyJhbGciOiJSUzI1NiJ9...", + "token_type": "Bearer", + "expires_in": 3600 +} +``` + +This route goes straight to the key manager and doesn't involve the portal, which makes it the one to script in continuous integration (CI). + +## Invoke the API + +Send the token as a bearer token: + +```bash +curl -X GET "https://api.example.com/orders/v1/orders" \ + -H "Authorization: Bearer <YOUR_ACCESS_TOKEN>" +``` + +If the API has subscription plans, add the subscription token too. It's what tells the gateway which plan's rate limit applies to you: + +```bash +curl -X GET "https://api.example.com/orders/v1/orders" \ + -H "Authorization: Bearer <YOUR_ACCESS_TOKEN>" \ + -H "Subscription-Key: <YOUR_SUBSCRIPTION_TOKEN>" +``` + +!!! note + Both header names come from the API's specification. Check its `securitySchemes` section for the authorization scheme, and its subscription-key parameter for the second header, rather than assuming the names above. + +## Revoke a client ID + +On the **Credentials** tab, click **Revoke keys**. This removes every credential the portal holds for that key manager and key type. + +Two things it doesn't do: it doesn't deregister or delete the OAuth application in the key manager, and it doesn't invalidate tokens already issued—those stay valid until they expire. To kill the OAuth application itself or revoke a live token, use the key manager's own console or revocation endpoint. + +## Related + +- [Consume an API](overview.md): which credentials an API expects, and how to tell +- [Manage Applications](manage-applications.md): the container for client IDs +- [Manage Subscriptions](manage-subscriptions.md): subscribe before generating credentials +- [Consume an API Secured with an API Key](api-key.md): the alternative for API-key-secured APIs +- [Key Manager Integration](../admin-settings/key-manager-integration.md): admin guide for key manager setup diff --git a/en/docs/api-portal/next/consume-an-api/overview.md b/en/docs/api-portal/next/consume-an-api/overview.md new file mode 100644 index 000000000..f3190eb22 --- /dev/null +++ b/en/docs/api-portal/next/consume-an-api/overview.md @@ -0,0 +1,69 @@ +--- +title: "Consume an API: which credentials you need" +description: "Work out which of the three credentials an API expects—a subscription token, an API key, or an OAuth2 access token—and how to get each one." +canonical_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/consume-an-api/overview.md +tags: + - cloud + - api-portal + - authentication +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "concept" +--- + +# Consume an API + +Calling an API published in the API Portal & MCP Hub takes two things: the endpoint URL, and the right credentials in the right headers. This page covers the credentials—which ones exist, how to tell which an API expects, and where to get each. + +Three distinct credentials are involved. The portal issues the subscription token and the API key; OAuth2 access tokens are issued by a key manager, which the portal only proxies requests to. They aren't alternatives to each other—an API can require one, two, or all three at once. + +| Credential | What it proves | Where it comes from | Header | +|---|---|---|---| +| Subscription token | You're subscribed to this API under a plan, so the gateway knows which rate limit to apply | Subscribing to a plan on the API's overview page | The header named in the API's specification, commonly `Subscription-Key` | +| API key | You're an authorized caller of this specific API | The **API Keys** page for that API | `apikey`, or the header named in the specification | +| OAuth2 access token | Your application holds valid client credentials | Issued by a key manager; requested through your application's **Manage Keys** page | `Authorization: Bearer <token>` | + +## Work out what an API expects + +Read the API's own specification—it's authoritative, and the portal derives its UI from it. + +1. Open the API and click **Documentation**, then **API Definition**. +2. Read `securitySchemes` for what the API *defines*: a scheme of type `oauth2` means an access token, and one of type `apiKey` names its exact header in the `name` field. +3. Read the `security` entries for what it *requires*. A root-level `security` block applies to every operation; an operation can override it with its own. A scheme that's defined but never referenced isn't required. +4. Check for a subscription-key parameter. When the specification declares one, the portal shows a subscription token after you subscribe, along with the header to send it in. + +Two shortcuts on the API's [overview page](../discover-apis/browse-apis.md#open-an-api) tell you the same thing faster: + +- An **API Keys** button appears for REST, WebSocket, and WebSub APIs whose specification declares API key security. The portal offers no key generation in the UI for GraphQL, SOAP, or MCP artifacts, even when their specification declares an `apiKey` scheme. For those, use the Management API: [API Keys](../rest-api/api-keys.md) for GraphQL and SOAP APIs, and [MCP Server Keys](../rest-api/mcp-server-keys.md) for MCP servers. +- A **Subscription plans** panel appears only for APIs with plans, which is what a subscription token comes from. + +The per-API Markdown document at `/api-portal/{orgName}/views/{viewName}/api/{apiHandle}.md` spells out the authentication steps in prose, generated from the same specification. It states those steps directly, so you don't cross-reference the specification yourself. + +## Get each credential + +### Subscription token + +[Subscribe to the API](manage-subscriptions.md) under a plan. The portal shows the token once, in a dialog, right after the subscription is created. + +You can come back to it later: on the API's overview page, click **View subscription** on the plan you hold, then reveal or copy the token. The same dialog lets you regenerate the token—which invalidates the old one immediately—suspend the subscription, or unsubscribe. + +### API key + +Generate one from the **API Keys** page for that API, reachable from the **API Keys** button on the overview page or the sidebar submenu. See [Manage API Keys](manage-api-keys.md) for the generate, rotate, and revoke lifecycle, and [Consume an API Secured with an API Key](api-key.md) for using it in a request. + +### OAuth2 access token + +This one needs the most setup, because the OAuth application lives in a key manager rather than in the portal. In outline: create the OAuth application in your key manager, [create an application](manage-applications.md) in the portal, link the client ID to it, then generate a token. See [Consume an API Secured with OAuth2](oauth2.md) for the full sequence. + +## Try before you wire it up + +For REST APIs, the **Try It** console on the [documentation page](../discover-apis/browse-apis.md#read-the-specification-and-try-it) sends real requests from your browser. Paste the same credentials you'd use from code—it's the fastest way to confirm you have the right header names before writing a client. + +## Related + +- [Consume an API Secured with OAuth2](oauth2.md) +- [Consume an API Secured with an API Key](api-key.md) +- [Manage Subscriptions](manage-subscriptions.md): where the subscription token comes from +- [Manage API Keys](manage-api-keys.md): generate, rotate, and revoke keys +- [Manage Applications](manage-applications.md): the container for OAuth2 client IDs diff --git a/en/docs/api-portal/next/discover-apis/browse-apis.md b/en/docs/api-portal/next/discover-apis/browse-apis.md new file mode 100644 index 000000000..b5607d77c --- /dev/null +++ b/en/docs/api-portal/next/discover-apis/browse-apis.md @@ -0,0 +1,109 @@ +--- +title: "Browse APIs in the API Portal & MCP Hub" +description: "Browse and search published APIs in the API Portal & MCP Hub by name, version, type, description, tags, or documentation content." +canonical_url: https://wso2.com/api-platform/docs/api-portal/discover-apis/browse-apis/ +md_url: https://wso2.com/api-platform/docs/api-portal/discover-apis/browse-apis.md +tags: + - cloud + - api-portal + - discover-apis +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# Browse APIs + +The API listing page is where you browse everything published to the API Portal & MCP Hub and narrow it down to the API you need. + +## Browse the API listing + +Click **APIs** in the sidebar. The listing page shows a card for every published API, above a count of how many are listed. + +The listing shows one card per API, with the search bar above it: + +![API listing page with a search bar and four API cards, each showing type badges, tags, plan count, and a Subscribe button](../../../assets/img/standalone-devportal/discover-apis/api-search/api-listing-page.png) + +Each card shows: + +- The API's icon (or its initials), name, and version +- A **Type** badge: REST, GraphQL, WebSocket, WebSub, or SOAP +- An **AI Ready** badge when the API is visible to AI agents, and a **Deprecated** badge when the API has been deprecated +- The description, and any tags the publisher added +- The number of subscription plans and a **Subscribe** button, when the API has plans +- A **Subscribed** ribbon, when you already hold a subscription to the API + +Click a card to open the API. Model Context Protocol (MCP) servers are listed separately — click **MCP Servers** in the sidebar to browse them the same way. + +!!! note + A listing covers one [view](../admin-settings/manage-views.md), and an API appears in it only if one of the API's labels is mapped to that view. If an API you expect is missing, ask your portal admin to check its labels. + +## Search for an API + +1. Type a term into the search bar at the top of the listing page. +2. Press <kbd>Enter</kbd>. + +The page reloads with your term applied as a `query` parameter, and the results bar reports how many APIs matched. To return to the full listing, clear the search bar and press <kbd>Enter</kbd> again. + +![Search bar with "Naviga" query returning a single matching APIs count of 1 and the Navigation API WebSocket card](../../../assets/img/standalone-devportal/discover-apis/api-search/search-result.png) + +### What a search term matches + +A search takes one free-text term rather than a set of separate fields. Which fields it compares that term against depends on the database the portal runs on—see the per-database differences below. + +| Matched against | Example | +|---|---| +| Name | `Navigation` finds the Navigation API | +| Version | `v3.5` finds every API published at that version | +| Description | `catalog` finds any API described as a catalog | +| Type | `RestApi` finds the REST APIs | +| Tags | `finance` finds every API tagged `finance` | +| Attached documents and the API specification | `webhook` finds an API whose getting-started guide mentions webhooks | + +Two details depend on which database backs your deployment: + +- **PostgreSQL** searches attached documents and the API specification alongside the metadata, and matches whole words and their grammatical variants through English full-text search. +- **SQLite** (the default) and **SQL Server** search the metadata and tags only, and match substrings, so `Naviga` finds the Navigation API. + +## Open an API + +Clicking a card opens the API's overview page, which gathers its endpoints, operations, and plans on one screen: + +![API overview page with header badges, endpoint URLs, a resources list, and a subscription plans panel](../../../assets/img/standalone-devportal/discover-apis/api-overview/api-overview.png) + +What the page shows depends on the API type. **Resources** lists every operation for REST and SOAP APIs; **Channels** replaces it for WebSocket and WebSub, where WebSocket channels carry both a PUB and a SUB badge and WebSub channels carry SUB only. **Scopes** appears for REST and SOAP. GraphQL APIs show **Endpoints** alone — their operations live in the schema, which you reach through **Documentation**. + +Two header buttons appear conditionally rather than always: + +- **API Keys** — only for REST, WebSocket, and WebSub APIs whose specification declares API key security. Never for GraphQL, SOAP, or MCP artifacts. +- **Try with AI** — only when the API is agent-visible. It opens a ready-made prompt that briefs an agent on the API using its [machine-readable documentation](../ai-agent-discovery.md); copy it, download it as a `.txt` file, or send it straight to an assistant with **Run in Claude**. + +Each plan in the **Subscription plans** panel carries its own **Subscribe** or **View subscription** button — see [Manage Subscriptions](../consume-an-api/manage-subscriptions.md) for the full flow. + +## Read the specification and try it + +**Documentation** in the header opens the API's specification alongside any guides the publisher attached. The viewer depends on the API type: + +| API type | Viewer | What you can do | +|---|---|---| +| REST | OpenAPI reference | Read every operation, parameter, and response schema, and call operations from the built-in **Try It** console | +| GraphQL | Schema viewer | Browse types, queries, and mutations. **Tryout** opens GraphiQL, with an endpoint selector and fields for an OAuth2 token or API key | +| WebSocket, WebSub | AsyncAPI viewer | Read the channels and message payloads. **Tryout** opens a client that connects to the endpoint | +| MCP server | MCP Playground | Inspect the server's tools and invoke them with a bearer token | + +!!! note + SOAP APIs have no **Documentation** button. Their overview page offers a **Download** button for the WSDL file instead. + +The **Try It** console calls the endpoint straight from your browser, so **the API's gateway has to return CORS headers for the portal's origin**. Supply every credential the operation requires, the same way your client would: an `Authorization: Bearer` header for OAuth2, the API key header for key-secured APIs, and — where the API declares one — the subscription header its specification names. + +Attached guides render in the same pane and cover what a specification can't: authentication walkthroughs, worked examples, known limitations. Every one is also served as raw Markdown for AI agents. + +![Attached document showing an authentication headers table, a subscription plans table, and numbered steps for consuming the API](../../../assets/img/standalone-devportal/discover-apis/api-documentations/api-doc-md.png) + +## Related + +- [APIs](overview.md): what an API is in the portal, and the types it publishes +- [Which Credentials You Need](../consume-an-api/overview.md): work out what an API expects before you call it +- [Manage Subscriptions](../consume-an-api/manage-subscriptions.md): subscribe, switch plans, and manage your subscription token +- [Customize an API's Content](../admin-settings/api-content.md): replace the generated page body with your own +- [Manage Views](../admin-settings/manage-views.md): how admins decide which APIs a view lists diff --git a/en/docs/api-portal/next/discover-apis/overview.md b/en/docs/api-portal/next/discover-apis/overview.md new file mode 100644 index 000000000..744917309 --- /dev/null +++ b/en/docs/api-portal/next/discover-apis/overview.md @@ -0,0 +1,77 @@ +--- +title: "APIs in the API Portal & MCP Hub" +description: "What an API is in the API Portal & MCP Hub, the API types it publishes, the two routes APIs take into the catalog, and how they differ from MCP servers." +canonical_url: https://wso2.com/api-platform/docs/api-portal/discover-apis/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/discover-apis/overview.md +tags: + - cloud + - api-portal + - apis +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "concept" +--- + +# APIs + +An **API** is an entry in the portal catalog that a developer can discover, subscribe to, and call from their own application code. Each one carries a contract, a landing page, documentation, and the subscription plans it offers. + +APIs sit in the same catalog as MCP servers and share its subscription and credential machinery. What differs is the contract they publish and how a consumer reaches them. + +## API types + +The portal publishes five API types, each with its own contract format: + +| Type | Contract | +|---|---| +| REST | OpenAPI, as `.json`, `.yaml`, or `.yml` | +| WebSocket | AsyncAPI, as `.json`, `.yaml`, or `.yml` | +| WebSub | AsyncAPI, as `.json`, `.yaml`, or `.yml` | +| GraphQL | A GraphQL schema, as `.graphql` or `.gql` | +| SOAP | WSDL, as `.wsdl` or `.xml` | + +The type is fixed when the API is created and determines what its detail page shows and which interactive console it offers. + +## How APIs differ from MCP servers + +An MCP server is another artifact in the same catalog, so most of what you know about APIs carries over. The differences that matter: + +| | API | MCP server | +|---|---|---| +| Contract | OpenAPI, AsyncAPI, GraphQL SDL, or WSDL | A definition listing tools, resources, and prompts | +| Catalog page | **APIs** in the sidebar | **MCP Servers** in the sidebar | +| Detail page sections | Endpoints, Resources or Channels, Scopes | MCP Server URL, Tools, Resources, Prompts | +| Interactive console | Try It, or a type-specific tryout client | MCP Playground | +| Consumed by | Application code you write | An MCP client, configured with the server's URL | +| Subscribed | Through an application that holds the credentials | Directly, without an application | +| Agent visibility | The publisher sets it per API | Always agent-visible; the catalog marks every server **AI Ready** | + +Everything else works the same way. APIs carry tags, labels, icons, subscription plans, and attached documents, and appear in the portal's [machine-readable endpoints](../ai-agent-discovery.md) when the publisher marks them agent-visible. + +!!! note + A portal only serves APIs when its operator lists `apis` in `enabled_types`. Leave it out and the sidebar entry, the catalog, and every API route disappear. See [Artifact types](../setting-up/artifact-types.md). + +## How APIs reach the catalog + +Two routes put an API in the catalog. + +### Registered by an admin + +An admin adds the API through **Settings** → **APIs**, choosing the type, supplying the details and endpoints, uploading the contract, and attaching documentation. See [Manage APIs](../admin-settings/manage-apis.md). + +### Created through the Management API + +The same artifact can be created programmatically, which is the route automation and CI/CD use. It is also the **only** way to add a SOAP API: the admin wizard's type selector offers no SOAP option, so those are posted with `type: SOAP` and a WSDL definition. See [APIs](../rest-api/apis.md) in the Management API reference. + +## Where to go next + +- [Browse APIs](browse-apis.md): search the catalog, open an API, and read its specification +- [Which Credentials You Need](../consume-an-api/overview.md): work out what an API expects before you call it + +## Related + +- [Manage APIs](../admin-settings/manage-apis.md): the admin side of adding and publishing APIs +- [MCP Servers](../mcp-servers/overview.md): the other half of the catalog +- [AI Agent Discovery](../ai-agent-discovery.md): the machine-readable endpoints that expose APIs to agents +- [Artifact types](../setting-up/artifact-types.md): whether this deployment serves APIs +- [Concepts](../concepts.md): how APIs fit alongside MCP servers, plans, subscriptions, and keys diff --git a/en/docs/api-portal/next/getting-started.md b/en/docs/api-portal/next/getting-started.md new file mode 100644 index 000000000..1646b4554 --- /dev/null +++ b/en/docs/api-portal/next/getting-started.md @@ -0,0 +1,253 @@ +--- +title: "Get started with API Portal & MCP Hub" +description: "Run the API Portal & MCP Hub locally with Docker Compose, sign in, and publish your first API to the catalog." +canonical_url: https://wso2.com/api-platform/docs/api-portal/getting-started/ +md_url: https://wso2.com/api-platform/docs/api-portal/getting-started.md +tags: + - cloud + - api-portal + - quickstart +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-23 +content_type: "quickstart" +--- + +# Getting started + +The API Portal & MCP Hub is where developers discover, subscribe to, and consume the APIs and MCP servers you publish. This guide gets the API Portal & MCP Hub running locally with Docker Compose in a few minutes, then walks you through publishing your first API. + +## Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) with the Compose plugin (`docker compose version`) +- `openssl` on your `PATH` (used by the setup script to generate certificates and secrets) +- `curl` and `unzip` installed +- Ports **9543** (API Portal & MCP Hub) and **9243** (Platform API) available on your machine + +## Step 1: Download the API Portal & MCP Hub + +Run this command in your terminal to download and unzip the standalone API Portal distribution: + +```bash +curl -sLO https://github.com/wso2/api-platform/releases/download/api-portal%2Fv1.0.0/wso2apip-api-portal-1.0.0.zip && \ +unzip wso2apip-api-portal-1.0.0.zip +``` + +## Step 2: Run the setup script + +Navigate to the API Portal directory: + +```bash +cd wso2apip-api-portal-1.0.0 +``` +Run this command to set up the API Portal: + +```bash +./scripts/setup.sh +``` + +This one-time script provisions everything the containers need to start: + +- a self-signed TLS certificate under `resources/certificates/` +- the API Portal and Platform API secrets—the encryption keys, the session secret, and the RS256 JWT signing keypair—written as files under `resources/keys/` +- your admin credentials, bcrypt-hashed into `APIP_CP_ADMIN_USERNAME` and `APIP_CP_ADMIN_PASSWORD_HASH` in `api-platform.env` + +It also prompts you for an **admin username and password**. Press Enter at the password prompt to have a strong one generated for you—it's printed once at the end, so copy it before continuing. + +!!! warning "Save the printed admin credentials" + The admin password is shown only once and is never stored in plaintext—only its bcrypt hash is written to `api-platform.env`. If you lose it, remove `APIP_CP_ADMIN_USERNAME` and `APIP_CP_ADMIN_PASSWORD_HASH` from `api-platform.env` and rerun `./scripts/setup.sh` to generate a new one. + +!!! note "Re-running the script" + The script is idempotent—re-running it only fills in what's missing and never overwrites an existing value. To rotate a secret, remove it from `api-platform.env`, or delete the relevant file under `resources/certificates/` or `resources/keys/`, then re-run. + +## Step 3: Start the portal + +```bash +docker compose up +``` + +This starts the API Portal & MCP Hub backed by SQLite by default. On first boot, the database schema and a default organization (`default`) with a `default` view are created automatically. + +Verify the Platform API sidecar is healthy: + +```bash +curl -fk https://localhost:9243/health +``` + +## Step 4: Open the portal + +Navigate to: + +``` +https://localhost:9543/api-portal/default/views/default +``` + +You'll see the API Portal home page. + +![API Portal home page](../../assets/img/standalone-devportal/getting-started/home-page.png) + +Click **Log In** and sign in with the admin username and password from Step 2. + +!!! tip "Browser trust warning?" + The generated TLS certificate is self-signed. Click **Advanced > Proceed** to continue. + +You should see the default API catalog page. It stays empty until you add APIs—either seed the bundled samples or publish your own, both covered next. + +## Step 5: Seed sample APIs (optional) + +The fastest way to see a populated catalog is to deploy the bundled sample APIs and MCP servers: + +```bash +./scripts/seed-samples.sh +``` + +This deploys everything under `resources/samples/` into the `default` organization through the public REST API. It prompts for the admin username and password from Step 2—or set `ADMIN_USERNAME` / `ADMIN_PASSWORD` to skip the prompt. It's safe to re-run: samples that already exist (matched by name and version) are skipped. + +!!! note + Requires `curl`, `jq`, and `zip` on your `PATH`, and the portal must already be running (Step 3). + +Refresh the catalog page and the sample APIs appear as a grid of cards, each showing the API's name, version, type, and a **Subscribe** button. + +![API listing page showing the seeded sample APIs as a grid of cards](../../assets/img/standalone-devportal/getting-started/api-listing-page.png) + +To publish an API of your own instead, continue below. + +## Step 6: Publish your first API + +Publish an API by uploading a manifest and an OpenAPI definition. This example uses a Books API, whose backend is already hosted, so it works without deploying a gateway of your own. + +Create the API manifest: + +```yaml +# api.yaml +apiVersion: api-portal.api-platform.wso2.com/v1 +kind: RestApi + +metadata: + name: books-api-v1.0 + +spec: + type: REST + displayName: Books API + version: v1.0 + description: Sample reading-list API for tracking books and their reading status. Open access — no API key or subscription required. + status: PUBLISHED + referenceId: books-api-v1.0 + + tags: + - reading-list + - books + + labels: + - default + + subscriptionPlans: [] + + agentVisibility: VISIBLE + + businessInformation: + businessOwner: Platform Owner + businessOwnerEmail: support@example.com + technicalOwner: API Team + technicalOwnerEmail: architecture@example.com + + # Points at the hosted sample backend, so the API works in a fresh portal with + # no gateway deployed. Front it with a gateway by swapping these for its URL. + endpoints: + sandboxUrl: https://apis.bijira.dev/samples/reading-list-api-service/v1.0 + productionUrl: https://apis.bijira.dev/samples/reading-list-api-service/v1.0 +``` + +Create the OpenAPI definition: + +```yaml +# definition.yaml +openapi: 3.0.1 +info: + title: Books API + version: v1.0 + description: | + Track a personal reading list — add books, update their reading status, and + remove them when you're done. Open access: no API key required. +servers: + - url: https://apis.bijira.dev/samples/reading-list-api-service/v1.0 +components: + schemas: + Book: + type: object + required: [title, author, status] + properties: + id: + type: string + format: uuid + readOnly: true + title: + type: string + example: The Great Gatsby + author: + type: string + example: F. Scott Fitzgerald + status: + type: string + enum: [to_read, reading, read] +paths: + /books: + get: + summary: List books + responses: + '200': + description: OK. The reading list. + post: + summary: Add a book + responses: + '201': + description: Created. The newly added book. + /books/{id}: + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + get: + summary: Get a book + responses: + '200': + description: OK. The requested book. + put: + summary: Update a book + responses: + '200': + description: OK. The updated book. + delete: + summary: Remove a book + responses: + '204': + description: No Content. The book was removed. +``` + +Then get a bearer token from the Platform API using the admin credentials from Step 2, and publish: + +```bash +# Get a token from the Platform API (runs alongside the API Portal) +TOKEN=$(curl -sk -X POST "https://localhost:9243/api/portal/v0.9/auth/login" \ + -d "username=<admin-username>&password=<admin-password>" | jq -r .token) + +# Publish the API — the login is scoped to the "default" organization +curl -k -X POST "https://localhost:9543/api-portal/api/v0.9/apis" \ + -H "Authorization: Bearer $TOKEN" \ + -F "metadata=@api.yaml;type=application/yaml" \ + -F "definition=@definition.yaml;type=application/yaml" +``` + +Refresh the portal—the **Books API** now appears in the catalog. Select the **Books API** card to open its documentation and **Try It** console. + +## What's next + +- [Browse APIs](discover-apis/browse-apis.md): browse and search the catalog +- [MCP Servers](mcp-servers/overview.md): publish and connect to Model Context Protocol servers +- [AI Agent Discovery](ai-agent-discovery.md): the `llms.txt` and Markdown endpoints agents use +- [Manage Applications](consume-an-api/manage-applications.md): set up a container for OAuth2 credentials +- [Manage Subscriptions](consume-an-api/manage-subscriptions.md): subscribe to a published API under a plan +- [Consume an API Secured with an API Key](consume-an-api/api-key.md) or [Consume an API Secured with OAuth2](consume-an-api/oauth2.md) +- [Theming](admin-settings/theming.md): customize the portal's look and feel diff --git a/en/docs/api-portal/next/mcp-registry.md b/en/docs/api-portal/next/mcp-registry.md new file mode 100644 index 000000000..ed69d83b9 --- /dev/null +++ b/en/docs/api-portal/next/mcp-registry.md @@ -0,0 +1,222 @@ +--- +title: "MCP Registry API" +description: "Discover and publish MCP servers through the API Portal & MCP Hub's implementation of the Model Context Protocol registry specification." +canonical_url: https://wso2.com/api-platform/docs/api-portal/mcp-registry/ +md_url: https://wso2.com/api-platform/docs/api-portal/mcp-registry.md +tags: + - cloud + - api-portal + - mcp + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "reference" +--- + +# MCP registry API + +The portal implements the [Model Context Protocol registry specification](https://modelcontextprotocol.io/), so MCP-aware tooling can discover the servers it publishes and push new ones without going through the portal UI or the Management API. + +A server published through the registry becomes an ordinary catalog artifact: it appears under **MCP Servers**, can be subscribed to, and shows up in the portal's agent-facing endpoints. See [MCP Servers](overview.md). + +## Base path + +Every endpoint is scoped to an organization handle, and two mount paths serve the same router: + +```text +/api-portal/registry/{orgHandle}/v0.1/... +/api-portal/{orgHandle}/registry/v0.1/... +``` + +Requests naming an organization this instance doesn't serve get a JSON `404`. Responses are JSON throughout, including errors, which take the shape `{"error": "..."}`. + +Cross-origin `GET` requests are allowed from any origin, so a browser-based MCP client can read the discovery endpoints directly. + +## Discovery endpoints + +These three need no authentication. + +### List servers + +```text +GET /api-portal/registry/{orgHandle}/v0.1/servers +``` + +Four query parameters control paging and filtering: + +| Query parameter | Effect | +|---|---| +| `limit` | Page size. Defaults to 30, capped at 100 | +| `cursor` | Opaque cursor from a previous response's `metadata.nextCursor` | +| `search` | Case-insensitive substring match against the server's name | +| `include_deleted` | Set to `true` to include servers whose status is `deleted`. Defaults to `false` | + +Servers come back newest-published first, with servers that have no publish timestamp last. The response wraps the page in a `metadata` object: + +```json +{ + "servers": [ { "server": { }, "_meta": { } } ], + "metadata": { + "count": 30, + "nextCursor": "eyJvZmZzZXQiOjMwfQ" + } +} +``` + +`nextCursor` is present only when more results exist. Pass it back as `cursor` to fetch the next page. A cursor the portal can't decode returns `400`. + +### List a server's versions + +```text +GET /api-portal/registry/{orgHandle}/v0.1/servers/{serverName}/versions +``` + +Returns every version of one server, newest first. `include_deleted` works as above. URL-encode the slash in `{serverName}`—`example.com/travel-assistant` becomes `example.com%2Ftravel-assistant`. + +### Get one version + +```text +GET /api-portal/registry/{orgHandle}/v0.1/servers/{serverName}/versions/{version} +``` + +Returns a single version, including its tools, resources, and prompts. Deleted versions return `404` unless you pass `include_deleted=true`. + +## Response shape + +Every endpoint returns the same object per server: + +```json +{ + "server": { + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "example.com/travel-assistant", + "title": "Travel Assistant MCP", + "version": "1.0.0", + "description": "MCP server for travel planning tools.", + "remotes": [ + { "type": "streamable-http", "url": "https://your-mcp-host.example.com" } + ] + }, + "_meta": { + "io.modelcontextprotocol.registry/official": { + "status": "active", + "publishedAt": "2026-07-31T09:12:04.000Z", + "updatedAt": "2026-07-31T09:12:04.000Z", + "isLatest": true + }, + "io.api-platform/mcp-capabilities": { + "tools": [], + "resources": [], + "prompts": [] + } + } +} +``` + +Notes on the fields: + +- `remotes` falls back to a single `streamable-http` entry built from the server's production URL when the stored payload declares none. +- `status` is one of `active`, `deprecated`, or `deleted`, mapping to the portal's published, deprecated, and deleted states. +- `io.api-platform/mcp-capabilities` is a WSO2 extension and is present only on responses that load the server's schema—the list endpoint omits it, so fetch a specific version when you need the tool list. + +## Publishing endpoints + +These require a bearer token or an authenticated local-auth session, and the same `dp:mcp_server:*` scopes as the equivalent Management API operations. `dp:mcp_server:manage` satisfies any of them. + +| Endpoint | Scope | +|---|---| +| `POST /api-portal/registry/{orgHandle}/v0.1/publish` | `dp:mcp_server:create` to create, `dp:mcp_server:update` to update | +| `PUT /api-portal/registry/{orgHandle}/v0.1/servers/{serverName}/versions/{version}` | `dp:mcp_server:update` | +| `DELETE /api-portal/registry/{orgHandle}/v0.1/servers/{serverName}/versions/{version}` | `dp:mcp_server:delete` | +| `PATCH /api-portal/registry/{orgHandle}/v0.1/servers/{serverName}/versions/{version}/status` | `dp:mcp_server:update` | +| `PATCH /api-portal/registry/{orgHandle}/v0.1/servers/{serverName}/status` | `dp:mcp_server:update` | + +### Publish a server + +```text +POST /api-portal/registry/{orgHandle}/v0.1/publish +``` + +`publish` is an upsert, keyed on the server name and version. It returns `201` when it creates a server and `200` when it updates one—and it checks the scope for the operation it's actually about to perform, so a token holding only `dp:mcp_server:create` gets `403` when the target version already exists. + +```json +{ + "name": "example.com/travel-assistant", + "title": "Travel Assistant MCP", + "version": "1.0.0", + "description": "MCP server for travel planning tools.", + "remotes": [ + { "type": "streamable-http", "url": "https://your-mcp-host.example.com" } + ], + "_meta": { + "io.api-platform/mcp-capabilities": { + "tools": [ + { + "name": "search_flights", + "description": "Find available flights for a given origin, destination, and travel date.", + "inputSchema": { "type": "object", "properties": {} } + } + ], + "resources": [], + "prompts": [] + }, + "io.api-platform/proxy-info": { "id": "travel-assistant" } + } +} +``` + +The payload is validated before anything is written. A `400` comes back when: + +- `name` is missing, isn't a string, is shorter than 3 or longer than 200 characters, or doesn't match the reverse-DNS `namespace/server` pattern +- `description` is missing or isn't a string +- `version` is missing, is the reserved value `latest`, or looks like a range rather than a specific version—anything starting with `^`, `~`, `>`, `<`, or `>=`, or containing `*` or an `x` placeholder + +Both `_meta` entries are optional. Omitting `io.api-platform/mcp-capabilities` on an update leaves the stored tool schema untouched; omitting it on a create stores an empty one. `io.api-platform/proxy-info.id` is an additional identifier the portal matches on, alongside the name. + +Newly published servers are mapped to the `default` label, so they appear in every view that includes it. + +### Update a version + +```text +PUT /api-portal/registry/{orgHandle}/v0.1/servers/{serverName}/versions/{version} +``` + +Takes the same body as `publish`, and the `version` field in the body has to match the one in the path—a mismatch returns `400`. + +### Delete a version + +```text +DELETE /api-portal/registry/{orgHandle}/v0.1/servers/{serverName}/versions/{version} +``` + +A soft delete. The version's status becomes `deleted` and it drops out of the discovery endpoints unless you ask for `include_deleted=true`. The row itself stays. + +### Change status + +```text +PATCH /api-portal/registry/{orgHandle}/v0.1/servers/{serverName}/versions/{version}/status +PATCH /api-portal/registry/{orgHandle}/v0.1/servers/{serverName}/status +``` + +Both take `{"status": "active" | "deprecated" | "deleted"}`. The first changes one version; the second changes every version of the server at once. Any other value returns `400`, and so does setting a version to the status it already has. + +## Registry vs. the management API + +The portal exposes MCP servers through two different APIs, and they aren't interchangeable: + +| | MCP Registry API | [Management API](rest-api/mcp-servers.md) | +|---|---|---| +| Path | `/api-portal/registry/{orgHandle}/v0.1` | `/api-portal/api/v0.9` | +| Shape | Model Context Protocol registry specification | The portal's own resource model | +| Discovery auth | None | Bearer token | +| Identified by | Reverse-DNS name plus version | Portal handle | +| Also manages | Nothing else | Content, documents, and [keys](rest-api/mcp-server-keys.md) | + +Use the registry when you want MCP-standard tooling to interoperate with the hub. Use the Management API when you're automating the portal itself. + +## Related + +- [MCP Servers](overview.md): how registry-published servers behave in the catalog +- [Browse MCP Servers](mcp-servers/browse-mcp-servers.md): the same servers, in the portal UI +- [MCP Servers (Management API)](rest-api/mcp-servers.md): the portal's own CRUD API +- [Authentication](rest-api/authentication.md): how to get a bearer token for the write endpoints diff --git a/en/docs/api-portal/next/mcp-servers/browse-mcp-servers.md b/en/docs/api-portal/next/mcp-servers/browse-mcp-servers.md new file mode 100644 index 000000000..b82067286 --- /dev/null +++ b/en/docs/api-portal/next/mcp-servers/browse-mcp-servers.md @@ -0,0 +1,91 @@ +--- +title: "Browse MCP servers in the API Portal & MCP Hub" +description: "Browse and search the MCP catalog, read a server's tools, resources, and prompts, and invoke them from the MCP Playground." +canonical_url: https://wso2.com/api-platform/docs/api-portal/mcp-servers/browse-mcp-servers/ +md_url: https://wso2.com/api-platform/docs/api-portal/mcp-servers/browse-mcp-servers.md +tags: + - cloud + - api-portal + - mcp +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# Browse MCP servers + +MCP servers have their own catalog, separate from the API listing but laid out the same way. + +## Browse the catalog + +Click **MCP Servers** in the sidebar. Each published server gets a card showing: + +- Its icon (or initials), title, and version +- An **MCP** badge, and an **AI Ready** badge—every MCP server in the catalog is agent-discoverable +- A **Deprecated** badge, when the server has been deprecated +- The description and any tags +- The number of subscription plans and a **Subscribe** button, when the server has plans +- A **Subscribed** ribbon, when you already hold a subscription + +Click a card to open the server. + +## Search + +Type a term into the search bar and press <kbd>Enter</kbd>. Search works exactly as it does for APIs—one free-text term matched against the server's metadata and tags, and on PostgreSQL against its attached documents too. See [Browse APIs](../discover-apis/browse-apis.md#what-a-search-term-matches) for the details and the per-database differences. + +Like the API listing, a catalog covers one [view](../admin-settings/manage-views.md), and a server appears only if one of its labels is mapped to that view. + +## Read a server + +The server page opens with a header carrying the title, version, **MCP** badge, description, and tags, then splits into a main column and a sidebar. + +### Main column + +The main column stacks the server's identity and everything it exposes: + +| Section | Contents | +|---|---| +| **MCP Server URL** | The server's production URL, with a copy button | +| **Tools** | Every tool the server exposes, each expandable to show its description and input schema as JSON | +| **Resources** | Every resource, with its description, URI, and MIME type | +| **Prompts** | Every prompt, with its description and argument schema | + +Each section shows a count in its header and is omitted when the server declares nothing of that kind. The tool, resource, and prompt entries come from the server's definition, so this page is the authoritative list of what an agent can call. + +### Sidebar + +The sidebar holds an **MCP Server Configuration** snippet—a ready-made JSON block naming the server, its URL, and a bearer token placeholder—with a copy button. Subscription plans appear below it when the server has any. See [Connect to an MCP Server](connect-to-an-mcp-server.md) for what to do with both. + +### Header buttons + +- **Subscribe**: jumps to the plans in the sidebar. Shown when the server has plans and you aren't subscribed yet +- **Documentation**: opens the documentation page. + +## Try tools in the Playground + +Click **Documentation** on a server to open its documentation page. The left pane lists a **SPECIFICATION** group containing **MCP Playground**, plus a group for each type of document the publisher attached. + +The playground connects to the server's URL and lets you list and invoke its tools interactively. It expects a bearer token, so have one ready—see [Connect to an MCP Server](connect-to-an-mcp-server.md). + +Attached documents render in the same pane, exactly as they do for an API. See [API Documentation](../discover-apis/browse-apis.md#read-the-specification-and-try-it) for how that pane works. + +## What agents see + +Every MCP server in the catalog is exposed through the portal's machine-readable endpoints: + +| Endpoint | Returns | +|---|---| +| `/api-portal/{orgName}/views/{viewName}/mcps.md` | Every agent-visible MCP server as one Markdown document | +| `/api-portal/{orgName}/views/{viewName}/mcp/{apiHandle}.md` | One server in Markdown: metadata, plans, attached documents, and its full tool, resource, and prompt list | +| `/api-portal/{orgName}/views/{viewName}/mcp/{apiHandle}/docs/specification.json` | The raw tool, resource, and prompt schema | + +Servers also appear in the portal's `llms.txt` index, under an **MCPs** section. See [AI Agent Discovery](../ai-agent-discovery.md). + +For programmatic discovery in the Model Context Protocol registry format, use the [MCP Registry API](../mcp-registry.md) instead. + +## Related + +- [Connect to an MCP Server](connect-to-an-mcp-server.md): credentials and client configuration +- [MCP Servers](overview.md): what an MCP server is and how it reaches the catalog +- [Browse APIs](../discover-apis/browse-apis.md): how catalog search matches terms +- [AI Agent Discovery](../ai-agent-discovery.md): the full set of agent-facing endpoints diff --git a/en/docs/api-portal/next/mcp-servers/connect-to-an-mcp-server.md b/en/docs/api-portal/next/mcp-servers/connect-to-an-mcp-server.md new file mode 100644 index 000000000..02ce7caee --- /dev/null +++ b/en/docs/api-portal/next/mcp-servers/connect-to-an-mcp-server.md @@ -0,0 +1,91 @@ +--- +title: "Connect to an MCP server" +description: "Subscribe to an MCP server, obtain a bearer token, and paste the portal's configuration snippet into your MCP client." +canonical_url: https://wso2.com/api-platform/docs/api-portal/mcp-servers/connect-to-an-mcp-server/ +md_url: https://wso2.com/api-platform/docs/api-portal/mcp-servers/connect-to-an-mcp-server.md +tags: + - cloud + - api-portal + - mcp + - authentication +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "how-to" +--- + +# Connect to an MCP server + +Connecting an MCP client to a server published in the hub takes three things: the server's URL, a credential, and a config block your client understands. The server page gives you all three. + +## Subscribe + +If the server has subscription plans, subscribe before you connect: + +1. Open the server from **MCP Servers** in the sidebar. +2. Click **Subscribe** in the header, or go to the **Subscription plans** panel in the sidebar. +3. Click **Subscribe** on the plan you want. + +The subscription is created immediately and a dialog shows your **subscription token**. Copy it before closing—send it as the `Subscription-Key` header when calling the server. + +Subscriptions to MCP servers work exactly as they do for APIs: one per server, switchable between plans without changing the token, suspendable, and manageable from **Subscriptions** in the sidebar. See [Manage Subscriptions](../consume-an-api/manage-subscriptions.md). + +Servers with no plans need no subscription. Connect with whatever authentication the server itself expects. + +## Get a bearer token + +The configuration snippet expects an OAuth2 access token in an `Authorization: Bearer` header. Get one the same way you would for an OAuth2-secured API: + +1. [Create an application](../consume-an-api/manage-applications.md) in the portal. +2. Link a client ID from your key manager to it under **Manage Keys**. +3. Generate an access token from the **Generate Token** tab, or with the ready-made command on the **cURL** tab. + +[Consume an API Secured with OAuth2](../consume-an-api/oauth2.md) walks through the whole sequence. + +!!! note + The portal's **API Keys** pages don't cover MCP servers—the button never appears on a server page, and there's no MCP equivalent of the API Keys screen. MCP server keys exist only through the [Management API](../rest-api/mcp-server-keys.md). For everything driven from the UI, use an OAuth2 bearer token. + +## Configure your client + +The sidebar on every server page holds an **MCP Server Configuration** block, pre-filled with the server's name and URL: + +```json +{ + "servers": { + "travel-assistant-mcp": { + "url": "https://your-mcp-host.example.com", + "type": "http", + "headers": { + "Authorization": "Bearer ${token}" + } + } + } +} +``` + +1. Click the copy button on the block. +2. Paste it into your MCP client's configuration. +3. Replace `${token}` with the access token you generated, or wire it to wherever your client reads secrets from. +4. If the server has subscription plans, add your subscription token as a second header: + + ```json + "headers": { + "Authorization": "Bearer ${token}", + "Subscription-Key": "${subscriptionToken}" + } + ``` + +The `type` is `http`, so the client speaks streamable HTTP to the URL rather than launching a local process. + +!!! important + The snippet uses the server's URL exactly as the publisher registered it. The MCP Playground doesn't—it appends `/mcp` to that URL unless it already ends that way. If the snippet's URL doesn't connect, try it with `/mcp` on the end, and check the URL the playground reports. + +## Check the connection + +Before wiring the server into an agent, confirm the credentials work: open the server's **Documentation** page and use the **MCP Playground**. It connects with a bearer token you supply and invokes the server's tools. A successful call there confirms the token and the server, for that one request—it doesn't prove your client is configured correctly, so check your client's URL, transport, headers, and how it interpolates the token separately. + +## Related + +- [Browse MCP Servers](browse-mcp-servers.md): find a server and read its tools +- [Manage Subscriptions](../consume-an-api/manage-subscriptions.md): plans, tokens, switching, and unsubscribing +- [Consume an API Secured with OAuth2](../consume-an-api/oauth2.md): the full token-generation sequence +- [Manage Applications](../consume-an-api/manage-applications.md): where client IDs live diff --git a/en/docs/api-portal/next/mcp-servers/overview.md b/en/docs/api-portal/next/mcp-servers/overview.md new file mode 100644 index 000000000..943f87b2a --- /dev/null +++ b/en/docs/api-portal/next/mcp-servers/overview.md @@ -0,0 +1,68 @@ +--- +title: "MCP servers in the API Portal & MCP Hub" +description: "What an MCP server is in the API Portal & MCP Hub, the two routes servers take into the catalog, and how they differ from APIs." +canonical_url: https://wso2.com/api-platform/docs/api-portal/mcp-servers/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/mcp-servers/overview.md +tags: + - cloud + - api-portal + - mcp +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "concept" +--- + +# MCP servers + +A Model Context Protocol (MCP) server exposes capabilities an AI agent can call directly: **tools** it can invoke, **resources** it can read, and **prompts** it can reuse. + +The API Portal & MCP Hub publishes MCP servers next to your APIs. They share the same catalog, subscription, and credential machinery—this is the "MCP Hub" half of the product. + +## How MCP servers differ from APIs + +An MCP server is stored as another artifact in the same catalog, so most of what you know about APIs carries over. The differences that matter: + +| | API | MCP server | +|---|---|---| +| Contract | OpenAPI, AsyncAPI, GraphQL SDL, or WSDL | A definition listing tools, resources, and prompts | +| Catalog page | **APIs** in the sidebar | **MCP Servers** in the sidebar | +| Detail page sections | Endpoints, Resources or Channels, Scopes | MCP Server URL, Tools, Resources, Prompts | +| Interactive console | Try It, or a type-specific tryout client | MCP Playground | +| Consumed by | Application code you write | An MCP client, configured with the server's URL | +| Agent visibility | Publisher sets it per API | Always agent-visible; the catalog marks every server **AI Ready** | + +Everything else works the same way. MCP servers carry tags, labels, icons, subscription plans, and attached documents, appear in the portal's [machine-readable endpoints](../ai-agent-discovery.md), and are subscribed to directly rather than through an application. + +!!! note + A portal only serves MCP servers when its operator lists `mcp-servers` in `enabled_types`. Leave it out and the sidebar entry, the catalog, and every MCP route disappear. See [Artifact types](../setting-up/artifact-types.md). + +## How servers reach the hub + +Two routes put an MCP server in the catalog, and which one was used affects what you see. + +### Registered by an admin + +An admin adds the server through **Settings** → **MCP Servers**, using the same wizard as an API with the type preset to MCP. They supply the details, upload the definition listing the tools, resources, and prompts, and attach documentation. See [Manage MCP Servers](../admin-settings/manage-mcp-servers.md). + +The same thing happens when a server is created on a gateway and pushed to the portal—the artifact carries a reference back to that gateway proxy. + +### Published through the MCP registry + +A client posts the server to the portal's registry API, an implementation of the Model Context Protocol registry specification. The portal stores the result as a catalog artifact exactly like any other MCP server, mapped to the `default` label so it shows up in views that include it. + +Registry-published servers are identified by a reverse-DNS name such as `example.com/travel-assistant` rather than by a gateway reference, and they get one entry per version. Their generated Markdown documentation also omits the authentication walkthrough that other artifacts carry, because the registry payload doesn't describe a security scheme. + +See [MCP Registry API](../mcp-registry.md). + +## Where to go next + +- [Browse MCP Servers](browse-mcp-servers.md): browse the catalog, read a server's tools, and try them in the playground +- [Connect to an MCP Server](connect-to-an-mcp-server.md): subscribe, get credentials, and point an MCP client at the server +- [MCP Registry API](../mcp-registry.md): the discovery and publishing endpoints + +## Related + +- [Manage MCP Servers](../admin-settings/manage-mcp-servers.md): the admin side of adding and publishing servers +- [AI Agent Discovery](../ai-agent-discovery.md): the machine-readable endpoints that expose MCP servers to agents +- [Artifact types](../setting-up/artifact-types.md): whether this deployment serves MCP servers +- [Concepts](../concepts.md): how MCP servers fit alongside APIs, plans, subscriptions, and keys diff --git a/en/docs/api-portal/next/overview.md b/en/docs/api-portal/next/overview.md new file mode 100644 index 000000000..7abc100b6 --- /dev/null +++ b/en/docs/api-portal/next/overview.md @@ -0,0 +1,128 @@ +--- +title: "API Portal & MCP Hub overview" +description: "The API Portal & MCP Hub is a portal where API publishers expose APIs and MCP servers, and developers discover, subscribe, and consume them." +canonical_url: https://wso2.com/api-platform/docs/api-portal/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/overview.md +tags: + - cloud + - api-portal + - overview +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "overview" +--- + +# API Portal & MCP Hub overview + +The API Portal & MCP Hub is a web application that serves a catalog of APIs, MCP servers, and API workflows. It keeps its own database and exposes a Management REST API for everything the UI does. Publishers register artifacts in the portal, and developers browse them, subscribe under a plan, and generate credentials. + +The portal issues those credentials without enforcing them: it publishes signed events, and the gateway checks each call—so developers need no access to the gateway or the infrastructure behind it. + +New to the portal's building blocks? See [Concepts](concepts.md) for a glossary of the organization, views, labels, APIs, MCP servers, API workflows, subscription plans, applications, subscriptions, and API keys. + +## What the API Portal & MCP Hub manages + +### API and MCP catalog + +Browse and search REST, GraphQL, SOAP, WebSocket, and WebSub APIs, as well as MCP servers, with full documentation and a try-out console. + +- Full-text search by name, type, version, and description +- Per-API documentation, landing page content, icons, and banners +- Machine-readable discovery (`llms.txt`, per-API Markdown, OpenAPI/AsyncAPI specs) built for AI agent consumption + +Learn more in [Discover APIs](discover-apis/browse-apis.md), [MCP Servers](mcp-servers/overview.md), and [AI Agent Discovery](ai-agent-discovery.md). + +### MCP servers + +Publish Model Context Protocol servers alongside your APIs, each with its tools, resources, and prompts listed, an MCP Playground for invoking them, and a ready-made client configuration snippet. Servers arrive either through the admin UI or through the portal's implementation of the MCP registry specification. + +Learn more in [MCP Servers](mcp-servers/overview.md) and the [MCP Registry API](mcp-registry.md). + +### Developer applications + +Logical containers for OAuth2 credentials. A developer can create multiple applications—for example, one per client or environment—each linked to independent OAuth2 client IDs. + +Learn more in [Manage Applications](consume-an-api/manage-applications.md). + +### Subscriptions and plans + +Developers subscribe directly to an API or MCP server under a named plan (for example, Gold or Bronze) that enforces rate limits and quotas. No application is required to subscribe. + +Learn more in [Manage Subscriptions](consume-an-api/manage-subscriptions.md) and, for admins, [Subscription Plans](admin-settings/subscription-plans.md). + +### API keys and OAuth2 credentials + +Generate, rotate, and revoke API keys bound to a specific API or MCP server. For OAuth2-secured APIs, link a client ID created in a key manager to an application and generate access tokens through the portal. + +Learn more in [Manage API Keys](consume-an-api/manage-api-keys.md), [Consume an API Secured with OAuth2](consume-an-api/oauth2.md), and, for admins, [Key Manager Integration](admin-settings/key-manager-integration.md). + +### API workflows + +Multi-step API call sequences authored in [Arazzo format](https://spec.openapis.org/arazzo/latest.html) and published for both human developers and AI agents to discover and follow. + +Learn more in [API Workflows](api-workflows.md). + +### Theming + +Give a view its own colors, page shell, header, footer, and page markup by uploading a theme. A theme is a partial copy of the default template tree, so a re-color is one file and everything you omit falls back to the default. + +Learn more in [Theming](admin-settings/theming.md), and [Apply a Theme](admin-settings/theming.md) for the upload panel. + +### Admin settings + +The **Settings** page is where admins configure everything above from one place: organization details, views, labels, subscription plans, key managers, the API and MCP catalog, webhooks, LLM instructions, and API workflows. + +| Section | Configure | +|---|---| +| [Organization Settings](admin-settings/organization-settings.md) | Display name, business owner contact, IDP reference ID | +| [Manage Views](admin-settings/manage-views.md) | Filtered, branded subsets of the catalog for different audiences | +| [Manage Labels](admin-settings/manage-labels.md) | Tags that control which views an API or MCP server appears in | +| [Subscription Plans](admin-settings/subscription-plans.md) | Rate/quota tiers applications can subscribe to | +| [Key Manager Integration](admin-settings/key-manager-integration.md) | OAuth2 authorization servers used to issue access tokens | +| [Manage APIs](admin-settings/manage-apis.md) | Add, edit, publish, deprecate, and delete APIs | +| [Manage MCP Servers](admin-settings/manage-mcp-servers.md) | Add, edit, publish, deprecate, and delete MCP servers | +| [Webhook Integration](admin-settings/webhook-integration.md) | Endpoints notified when API keys or subscriptions change | +| [LLM Instructions](admin-settings/llm-instructions.md) | Portal-level context published to `llms.txt` for AI agents | +| [Managing API Workflows](admin-settings/manage-api-workflows.md) | Author, publish, and control visibility of API workflows | +| [Apply a Theme](admin-settings/theming.md) | Upload, download, and reset a view's custom theme | + +## Gateway-agnostic, unified developer experience + +The portal holds no gateway-specific logic. Instead, it emits signed webhook events: whenever a developer generates an API key, subscribes, or revokes a key, the portal fires a signed HTTP POST to every registered subscriber. + +A subscriber is any endpoint you register. Most deployments point it at the Platform API control plane, which verifies the signature, decrypts the credential, persists it, and propagates it to every gateway the API is deployed to. A gateway that consumes the events itself can subscribe directly instead, as can a handler of your own. + +This means you can: + +- Serve every gateway type your control plane supports, through one integration +- Replace or swap a gateway without changing how developers interact with the portal +- Run the portal standalone, with no live gateway required +- Provision systems beyond a gateway, by registering a handler that acts on the same events + +## Views + +Within your [organization](admin-settings/organization-settings.md) you can define multiple [views](admin-settings/manage-views.md) for different audiences—for example, one for internal teams and one for external partners. + +## Setting up + +Beyond the Docker Compose quick start, a production deployment typically needs: + +| Topic | Covers | +|---|---| +| [Authentication](setting-up/authentication/overview.md) | Local and OIDC authentication modes, with an Asgardeo identity-provider walkthrough | +| [Artifact types](setting-up/artifact-types.md) | Choose which artifact types—APIs, MCP servers, and API workflows—the portal serves | +| [Design Mode](admin-settings/design-mode.md) | Develop and preview themes/layouts offline, without a database or IDP | + +## References + +| Topic | Covers | +|---|---| +| [Management API](rest-api/overview.md) | Full reference for every API Portal REST API resource | +| [Get a Bearer Token via curl](references/get-a-bearer-token-via-curl.md) | Scripted/CI access to the REST API under IDP mode | +| [Configurations](references/configurations.md) | Full `config.toml` field reference and environment interpolation | + +## Getting started + +To start using the API Portal & MCP Hub, follow the [Getting Started](getting-started.md) guide. + +For a summary of what this release includes, see [About this release](about-this-release.md). diff --git a/en/docs/api-portal/next/references/configurations.md b/en/docs/api-portal/next/references/configurations.md new file mode 100644 index 000000000..c071acd14 --- /dev/null +++ b/en/docs/api-portal/next/references/configurations.md @@ -0,0 +1,295 @@ +--- +title: "API Portal configuration reference" +description: "How the API Portal & MCP Hub loads config.toml, injects environment values through interpolation tokens, and the full reference of every supported configuration key." +canonical_url: https://wso2.com/api-platform/docs/api-portal/references/configurations/ +md_url: https://wso2.com/api-platform/docs/api-portal/references/configurations.md +tags: + - cloud + - api-portal + - configuration +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Configurations + +The API Portal & MCP Hub reads its configuration from `configs/config.toml`, layered over built-in defaults (`src/config/configDefaults.js`). This page is the full reference of every supported key. + +For how that file is loaded, how environment values and mounted files are injected through interpolation tokens, and how to keep sensitive values out of the file, see [Configuration and environment interpolation](../setting-up/configuration.md). + +{% raw %} + +## Server + +```toml +[api_portal.server] +base_url = '{{ env "APIP_AP_SERVER_BASE_URL" "https://localhost:9543" }}' +port = 9543 + +[api_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 | + +{% endraw %} + +## Logging + +```toml +[api_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 + +```toml +[api_portal.database] +driver = "sqlite" # sqlite | postgres | mssql +path = "./api-portal.db" # SQLite only +host = "localhost" # PostgreSQL / MSSQL only +port = 5432 # PostgreSQL / MSSQL only (1433 for MSSQL) +name = "api_portal" # 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 +``` + +!!! warning "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 + +```toml +[api_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 +``` + +`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 + +```toml +[api_portal.auth] +mode = "local" # local | idp + +[api_portal.auth.claim_mappings] +organization = "org_name" # claim carrying the org ID +roles = "roles" # claim carrying the user's roles +groups = "groups" + +[api_portal.auth.local] +platform_api_url = "" +public_key_path = "" # path to the Platform API's RS256 public key PEM +tls_skip_verify = false + +# The [api_portal.auth.idp] endpoints describe your own identity provider and have +# no default. issuer, authorization_url, token_url, client_id, and callback_url are +# required when mode = "idp"; the portal refuses to start without them. +[api_portal.auth.idp] +name = "my-idp" # friendly name, used in logs +issuer = "" +authorization_url = "" +token_url = "" +user_info_url = "" +client_id = "" +client_secret = "" +audience = "" +callback_url = "" +scope = "openid profile email" +sign_up_url = "" +logout_url = "" +logout_redirect_uri = "" +certificate = "" +jwks_url = "" +token_refresh_timeout_ms = 10000 +silent_sso = true # Enable silent SSO +org_callback = false # Redirect to the org's own landing page after login +``` + +See [Authentication](../setting-up/authentication/overview.md) for the authentication modes, and [Connect an identity provider](../setting-up/authentication/connect-an-identity-provider.md) for the value each `[api_portal.auth.idp]` key has to match in the IdP. + +### Authorization + +Authorization is configured in its own section, independent of `auth.mode`, because both the local and IDP branches read it. + +```toml +[api_portal.auth.authorization] +enabled = true +mode = "role" # scope | role +role_to_scope_mapping = "./resources/role-to-scope-mapping.yaml" +page_role_validation = false + +[api_portal.auth.authorization.portal_roles] +admin = "ap_admin" +subscriber = "ap_subscriber" +``` + +Five keys govern how a request's permissions are resolved: + +| Key | Default | Description | +|---|---|---| +| `authorization.enabled` | `true` | Master switch for Management API (`/api-portal/api/v0.9`) authorization. With `false`, any authenticated caller satisfies every operation's scope list—a development opt-out that logs a startup warning | +| `authorization.mode` | `role` | How a request's effective scopes are derived. `role` expands the token's roles claim through the mapping table and ignores the scope claim entirely, so a caller can't widen a role's grant by asking for extra scopes. `scope` reads the token's own scope claim—use it when the issuer mints `dp:*` scopes directly. Validated even when `enabled = false`, so a typo surfaces immediately | +| `authorization.role_to_scope_mapping` | `./resources/role-to-scope-mapping.yaml` | Path to the YAML grant table. Required when `mode = "role"`. Validated at startup against the portal's OpenAPI spec whenever it's set—an undeclared `dp:*` scope fails startup rather than surfacing later as a role that logs in and is denied every request | +| `authorization.page_role_validation` | `false` | Per-page role-tier gating. Separate from `enabled`, which governs REST scopes—one switch for both would mean turning page gating off also silently disabled REST enforcement | +| `authorization.portal_roles.admin` / `.subscriber` | `ap_admin` / `ap_subscriber` | The role names, as they appear in the roles claim, that grant each page-access tier. Point them at your IDP's role names, or at names in the mapping table to drive page gating and REST authorization from the same roles | + +!!! danger "Two retired keys abort startup" + Leaving either of these in `config.toml` fails startup by design—an ignored key would silently apply the default instead of what the file says. + + | Retired key | Replacement | + |---|---| + | `auth.role_validation` | `auth.authorization.page_role_validation` | + | `auth.idp.roles` | `auth.authorization.portal_roles` | + + Note that `role_validation` maps to `page_role_validation`, **not** to `authorization.enabled`. There was also a third role tier, `super_admin`; it gated pages this portal doesn't serve, so it was removed. + +## 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: + +```toml +# [api_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 + +```toml +[api_portal.organization] +handle = "default" # URL slug: /api-portal/{handle}/views/{viewName} +display_name = "Default" # Used only when first seeding the organization +auto_create_subscription_plans = true # Auto-create Bronze/Silver/Gold/Unlimited/AsyncUnlimited +``` + +These three keys describe the organization the instance serves: + +| Key | Description | +|---|---| +| `organization.handle` | The URL slug of the single organization this instance serves, and the pin every route is scoped against. Anything resolving to a different organization is rejected. In local-auth mode it must match the Platform API's organization id. In IDP mode it's also what the token's organization claim has to resolve to—see the note below | +| `organization.display_name` | Used only when seeding the organization for the first time. Never overwrites an existing name, so an admin's later edit in the settings UI survives restarts. Empty means "use the handle" | +| `organization.auto_create_subscription_plans` | Seeds Bronze, Silver, Gold, Unlimited, and AsyncUnlimited alongside the organization | + +Seeding runs on startup only if the organization doesn't already exist, so it's idempotent and safe to leave enabled. + +!!! note "Which token claim carries the organization" + The two authentication modes resolve it differently, so don't assume one claim covers both. + + - **Local auth** reads a fixed `org_handle` claim and compares it to `organization.handle`. + - **IDP mode** reads the claim named by `auth.claim_mappings.organization` (default `org_name`) and resolves it, accepting the organization's handle or its display name. The organization's IDP reference ID is seeded from `handle` and can't be changed afterward, so `handle` is the value to align the claim with—see [Make the organization claim resolve to your organization](../setting-up/authentication/connect-an-identity-provider.md#step-5-make-the-organization-claim-resolve-to-your-organization). + + +!!! note + `organization.default_name` is a deprecated alias for `handle`. It still resolves, with a startup warning—use `handle` in new configuration. + +## Artifacts + +```toml +[api_portal.artifacts] +enabled_types = ["apis", "mcp-servers", "api-workflows"] +``` + +An allowlist of the artifact types this portal serves. A type left out gets no navigation entry, no landing-page section, and `404`s on its routes. Valid entries are `apis`, `mcp-servers`, and `api-workflows`; an unrecognized entry aborts startup so a typo can't silently drop a type. Omit the section to serve all three. See [Artifact types](../setting-up/artifact-types.md). + +## Uploads + +Limits applied to every upload and to archive extraction—theme ZIPs, API specs, documents, and landing-page content. + +```toml +[api_portal.uploads] +max_bytes = 10485760 # 10 MiB — a single upload, or a single entry inside an archive +max_total_bytes = 52428800 # 50 MiB — total extracted size per archive +max_zip_entries = 500 +max_depth = 10 +``` + +These are the ceilings the Theming panel's "up to 10 MB" hint and the Manage APIs spec upload both derive from. `max_total_bytes`, `max_zip_entries`, and `max_depth` guard archive extraction against a decompression bomb, so raise them only deliberately. + +!!! note + `config-template.toml` documents this table, but the shipped `config.toml` omits it—so the built-in defaults above apply until you add the table to `config.toml` yourself. + +## Try-out proxy + +The try-it console calls an API's registered endpoint, which is a different origin from the portal. Rather than requiring every gateway to return CORS headers naming the portal, the panel can be pointed at a same-origin proxy that makes the call server-side. + +```toml +[api_portal.tryout] +enabled = true +allow_http_endpoints = true # false: only https:// endpoints may be called +allow_private_endpoints = false +tls_skip_verify = false # development only +timeout_ms = 15000 +max_request_bytes = 1048576 # 1 MiB +max_response_bytes = 5242880 # 5 MiB +``` + +The proxy's behavior and its safety limits are set by these keys: + +| Key | Default | Description | +|---|---|---| +| `tryout.enabled` | `true` | Whether the proxy is available | +| `tryout.allow_http_endpoints` | `true` | Whether cleartext `http://` endpoints may be called. Intended for local development; set it to `false` in production so only `https://` endpoints are reachable | +| `tryout.allow_private_endpoints` | `false` | Deny-by-default. The registered-endpoint allowlist can't protect against an endpoint registered to point at an internal service, so this denylist is the only control for that case. Set `true` when the gateway legitimately sits on a private address—a Docker Compose service name, a cluster IP, localhost—after confirming only intended services are reachable from the portal | +| `tryout.tls_skip_verify` | `false` | Development only | +| `tryout.timeout_ms` | `15000` | Per-request timeout | +| `tryout.max_request_bytes` | `1048576` | Request body ceiling. Exceeding it returns `413` | +| `tryout.max_response_bytes` | `5242880` | Response body ceiling | + +Two limits hold regardless of these settings: the proxy only calls URLs contained by one of the endpoints registered for that API, so a caller can't choose an arbitrary host; and link-local and cloud-metadata addresses such as `169.254.169.254` are refused at connection time. + +## Design mode + +```toml +# [api_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](../admin-settings/design-mode.md) for the full field reference and theme-development workflow. + +## Webhooks + +```toml +[api_portal.webhooks.delivery] +poll_interval_ms = 2000 +batch_size = 50 +signature_tolerance_sec = 300 +``` + +Global delivery tuning only—subscribers themselves are per-organization, managed on the [Webhook Integration](../admin-settings/webhook-integration.md) settings tab, not in this file. Each delivery is attempted exactly once; there's no retry or backoff. + +`signature_tolerance_sec` is the window the portal's own signature verifier accepts. See the [Webhook Event Catalog](webhook-event-catalog.md) for the signing algorithm. + +## Related + +- [Authentication](../setting-up/authentication/overview.md) +- [Artifact types](../setting-up/artifact-types.md) +- [Design Mode](../admin-settings/design-mode.md) +- [Webhook Event Catalog](webhook-event-catalog.md) +- [Get a Bearer Token via curl](get-a-bearer-token-via-curl.md) +- [Management API](../rest-api/overview.md) diff --git a/en/docs/api-portal/next/references/get-a-bearer-token-via-curl.md b/en/docs/api-portal/next/references/get-a-bearer-token-via-curl.md new file mode 100644 index 000000000..a09ed3231 --- /dev/null +++ b/en/docs/api-portal/next/references/get-a-bearer-token-via-curl.md @@ -0,0 +1,154 @@ +--- +title: "Get a Bearer token via curl (IdP mode)" +description: "Obtain a Bearer token for the API Portal REST API from the terminal, without a browser, when running in external IdP mode." +canonical_url: https://wso2.com/api-platform/docs/api-portal/references/get-a-bearer-token-via-curl/ +md_url: https://wso2.com/api-platform/docs/api-portal/references/get-a-bearer-token-via-curl.md +tags: + - cloud + - api-portal + - authentication + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "how-to" +--- + +# Getting a bearer token via curl (IdP mode) + +When the API Portal & MCP Hub is configured with an external identity provider (IdP) such as Asgardeo, REST API calls to `/api-portal/api/v0.9/*` must include an `Authorization: Bearer <token>` header. This guide obtains that token from the terminal, without going through the portal UI. One step still opens a browser: the IdP's own login and redirect. + +!!! note + If you're running in **local auth mode** instead (the default for local development), get a token from the Platform API directly—see [Getting Started](../getting-started.md)—no PKCE flow needed. + +## Prerequisites + +- An IdP is configured, with `api_portal.auth.idp.client_id` set in `config.toml`—see [Authentication](../setting-up/authentication/overview.md) +- Your user carries the privileges the operations need: a role the portal's grant table names when `auth.authorization.mode = "role"`, or the `dp:*` scopes themselves when it's `"scope"`—see [Choose how privileges reach the token](../setting-up/authentication/connect-an-identity-provider.md#step-3-choose-how-privileges-reach-the-token) +- You have the **client ID** and **client secret** from your IdP application +- You know your org's identifier (the `ORGANIZATION_IDENTIFIER` value used to scope the login, e.g. `sub`) + +## Flow: Authorization code + Proof Key for Code Exchange (PKCE) + +The API Portal application is a confidential Traditional Web App—it uses authorization code flow with PKCE and a client secret. You need to: + +1. Generate a PKCE code verifier and challenge +2. Open the authorization URL (paste into a browser) +3. Exchange the authorization code for a token + +## Step 1—generate PKCE values + +```bash +# Code verifier: 43–128 random URL-safe characters +CODE_VERIFIER=$(openssl rand -base64 64 | tr -d '=+/' | cut -c1-64) + +# Code challenge: SHA-256 of the verifier, base64url-encoded +CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '=') + +echo "CODE_VERIFIER=$CODE_VERIFIER" +echo "CODE_CHALLENGE=$CODE_CHALLENGE" +``` + +## Step 2—start a local redirect listener + +The IdP redirects back to a callback URI with the authorization code. Use `nc` to capture it: + +```bash +PORT=8080 +nc -l $PORT & +NC_PID=$! +``` + +!!! note + Register `http://localhost:8080` as an authorized redirect URI in your IdP application before proceeding. + +## Step 3—build the authorization URL and open it + +```bash +TENANT=<your-tenant> # e.g. dev1234 +CLIENT_ID=<api-portal-app-client-id> +ORG_IDENTIFIER=<org-identifier> # ORGANIZATION_IDENTIFIER value, e.g. "sub" +STATE=$(openssl rand -hex 16) + +AUTH_URL="https://api.asgardeo.io/t/${TENANT}/oauth2/authorize\ +?response_type=code\ +&client_id=${CLIENT_ID}\ +&redirect_uri=http://localhost:${PORT}\ +&scope=openid%20profile%20email%20dp:api:manage%20dp:application:manage%20dp:organization:manage%20dp:subscription:manage\ +&code_challenge=${CODE_CHALLENGE}\ +&code_challenge_method=S256\ +&state=${STATE}\ +&org=${ORG_IDENTIFIER}" + +echo "Open this URL in your browser:" +echo "$AUTH_URL" +``` + +Open the URL, log in, and approve. The browser is redirected to `http://localhost:8080?code=...&state=...`—the `nc` process captures the raw HTTP request. + +## Step 4—extract the authorization code + +```bash +# nc prints something like: +# GET /?code=abc123xyz&state=... HTTP/1.1 + +CODE=<paste-code-value-here> +kill $NC_PID 2>/dev/null +``` + +## Step 5—exchange the code for a token + +```bash +TOKEN_URL="https://api.asgardeo.io/t/${TENANT}/oauth2/token" +CLIENT_SECRET=<api-portal-app-client-secret> + +RESPONSE=$(curl -s -X POST "$TOKEN_URL" \ + -u "${CLIENT_ID}:${CLIENT_SECRET}" \ + -d "grant_type=authorization_code" \ + -d "code=${CODE}" \ + -d "redirect_uri=http://localhost:${PORT}" \ + -d "code_verifier=${CODE_VERIFIER}") + +echo "$RESPONSE" | jq . + +TOKEN=$(echo "$RESPONSE" | jq -r '.access_token') +echo "TOKEN=$TOKEN" +``` + +## Step 6—call the API + +```bash +# The org is resolved from the token's org claim (set via ORGANIZATION_IDENTIFIER +# during login in Step 3) — no org identifier needed in the request itself. +BASE="https://localhost:9543/api-portal/api/v0.9" + +curl -sk "${BASE}/apis" -H "Authorization: Bearer $TOKEN" | jq . +curl -sk "${BASE}/applications" -H "Authorization: Bearer $TOKEN" | jq . + +curl -sk -X POST "${BASE}/applications" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"displayName": "My CLI App", "description": "Created via API"}' | jq . +``` + +See the [Management API](../rest-api/overview.md) for the full set of available operations. + +## Troubleshooting + +| Symptom | Cause | Fix | +|---|---|---| +| `403 Missing organization claim in token` | Token has no org claim | Log in with `org=<ORGANIZATION_IDENTIFIER>` in the auth URL | +| `403 Forbidden` (organization) | Token's org claim doesn't resolve to the organization this portal serves. Unknown and foreign organizations return the same status, so the response can't be used to discover which organizations exist | Verify `ORGANIZATION_IDENTIFIER` matches the portal's `[api_portal.organization] handle` | +| `403 Forbidden` (scope error) | The request's effective scopes don't cover the operation | In `mode = "role"`, assign the user a role the portal's grant table names. In `mode = "scope"`, grant the operation's `dp:*` scope to the application in the IdP | +| `401 Authentication required` | Token expired or invalid | Re-run steps 1–5 for a fresh token | +| Token carries no `dp:*` scopes | Expected in `mode = "role"`—the portal derives scopes from the roles claim and ignores the token's own scope claim, so an absent `dp:*` scope isn't the fault | Check the roles claim instead. In the Asgardeo console, assign `dp_admin` for full access, or `dp_subscriber`. Only `mode = "scope"` requires the scopes in the token | +| `nc` gets no output | Redirect URI not registered in IdP | Add `http://localhost:8080` to authorized redirect URIs | + +## Token lifetime + +Asgardeo access tokens typically expire in 3600 seconds (1 hour). Re-run steps 1–5 for a new one—the API Portal & MCP Hub also supports refresh tokens, but from the terminal it's simpler to just re-authenticate. + +## Related + +- [Authentication](../setting-up/authentication/overview.md) +- [Management API](../rest-api/overview.md) +- [Configurations](configurations.md) diff --git a/en/docs/api-portal/next/references/webhook-event-catalog.md b/en/docs/api-portal/next/references/webhook-event-catalog.md new file mode 100644 index 000000000..772620a1c --- /dev/null +++ b/en/docs/api-portal/next/references/webhook-event-catalog.md @@ -0,0 +1,250 @@ +--- +title: "Webhook event catalog" +description: "Every webhook event the API Portal & MCP Hub publishes, with its payload fields, the delivery envelope, headers, signature verification, and field encryption." +canonical_url: https://wso2.com/api-platform/docs/api-portal/references/webhook-event-catalog/ +md_url: https://wso2.com/api-platform/docs/api-portal/references/webhook-event-catalog.md +tags: + - cloud + - api-portal + - webhooks + - reference +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "reference" +--- + +# Webhook event catalog + +The portal publishes twelve event types. This page gives the delivery envelope, the headers, how to verify and decrypt a delivery, and the exact `data` fields for every event. + +To register an endpoint and choose which events it receives, see [Webhook Integration](../admin-settings/webhook-integration.md). + +## The delivery envelope + +Every delivery is a `POST` with the same outer shape, whatever the event: + +```json +{ + "event_id": "6f1b0e42-2c9a-4f7e-9a1d-8f2b5c3d7e10", + "event_type": "apikey.generated", + "occurred_at": "2026-07-31T09:12:04.512Z", + "org": { "ref_id": "acme-org" }, + "encrypted_fields": ["key"], + "data": { + "key_id": "…", + "key": { "iv": "…", "tag": "…", "ciphertext": "…" } + } +} +``` + +Each envelope field means the following: + +| Field | Meaning | +|---|---| +| `event_id` | Unique id for the event. Stable across every subscriber that receives it—use it to deduplicate | +| `event_type` | One of the types in the catalog below | +| `occurred_at` | When the event was recorded, not when it was delivered | +| `org.ref_id` | The organization's control-plane reference id, falling back to its internal UUID when unset | +| `encrypted_fields` | Names of the keys in `data` that hold an encrypted envelope rather than a plain value. Empty for most events | +| `data` | The event's own fields, from the catalog below, plus one entry per encrypted field | + +## Headers + +Every delivery carries these headers, the last one only when the subscriber has a secret: + +| Header | Always sent | Value | +|---|---|---| +| `Content-Type` | Yes | `application/json` | +| `X-Api-Portal-Event` | Yes | The event type, so you can route without parsing the body | +| `X-Api-Portal-Event-Id` | Yes | Same value as `event_id` | +| `X-Api-Portal-Delivery-Id` | Yes | Unique per delivery attempt, per subscriber. Differs between two subscribers receiving the same event | +| `X-Api-Portal-Signature` | When the subscriber has a secret | See below | + +## Verify the signature + +The signature header looks like this: + +```text +X-Api-Portal-Signature: t=1785490324,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 +``` + +To verify: + +1. Split the header on `,` and read `t` (Unix seconds) and `v1` (hex digest). +2. Compute `HMAC-SHA256(secret, "<t>.<raw_body>")` over the **raw** request body, before any JSON parsing or re-serialization. +3. Compare against `v1` with a constant-time comparison. +4. Reject the delivery if `|now - t|` exceeds your tolerance. The portal's own verifier defaults to 300 seconds. + +## Decrypt sensitive fields + +Four events carry a credential. The credential never appears in plaintext in the body. It is never stored in the portal's event record either. Instead it is encrypted per subscriber at publish time. + +Each encrypted field in `data` is an object: + +```json +{ "iv": "<base64>", "tag": "<base64>", "ciphertext": "<base64>" } +``` + +The cipher is **AES-256-GCM**, under a key **derived from that subscriber's shared secret**—the same secret used for the signature. There is no separate key pair and no public key to configure. + +Derivation is HKDF, and every parameter has to match exactly: + +| Parameter | Value | +|---|---| +| KDF | HKDF with SHA3-256 | +| Input keying material | The subscriber's secret, as UTF-8 bytes | +| Salt | Empty (zero-length, not absent) | +| Info / context | The exact ASCII string `api-portal-webhook-field-encryption-v1` | +| Output length | 32 bytes, for AES-256 | + +The same derived key decrypts every encrypted field in every event for that subscriber—the key depends only on the secret, not on the event or field name. `iv` is a 12-byte GCM nonce and `tag` a 16-byte authentication tag, both base64, and both sent separately from `ciphertext`. + +Decrypting one field, in Node.js: + +```javascript +const crypto = require('crypto'); + +function decryptField(secret, { iv, tag, ciphertext }) { + const key = crypto.hkdfSync( + 'sha3-256', secret, Buffer.alloc(0), + 'api-portal-webhook-field-encryption-v1', 32 + ); + const decipher = crypto.createDecipheriv( + 'aes-256-gcm', Buffer.from(key), Buffer.from(iv, 'base64') + ); + decipher.setAuthTag(Buffer.from(tag, 'base64')); + return Buffer.concat([ + decipher.update(Buffer.from(ciphertext, 'base64')), + decipher.final(), + ]).toString('utf8'); +} + +// const apiKey = decryptField(mySecret, body.data.key); +``` + +Runtimes that expect the tag appended to the ciphertext—Go's `crypto/cipher` GCM among them—need the two concatenated before opening. + +!!! important + A subscriber with no secret still receives these events, just without the encrypted fields—`encrypted_fields` comes back empty and the credential is absent entirely. It is never downgraded to plaintext. Set a secret on any subscriber that needs to read credentials. + +## Delivery semantics + +- **A 2xx response means delivered.** Any other status, a connection error, or a timeout marks the delivery failed. +- **There is no retry.** A failure is terminal—the portal records it and moves on. Your endpoint has to be reliable, and it has to answer within the subscriber's timeout (5000 ms unless you change it). +- **Deliveries are per subscriber.** One event matching three subscribers produces three deliveries, each with its own delivery id, signature, and encrypted fields. +- **Order isn't guaranteed.** Events are dispatched from a polled queue, so use `occurred_at` rather than arrival order when sequence matters. +- **A delivery left in flight by a stopped worker** is marked failed after five minutes rather than hanging indefinitely. + +Delivery history is readable through the Management API—see [Webhook Events](../rest-api/webhook-events.md). + +## Subscription events + +All five share the same base `data`, whose fields are: + +| Field | Notes | +|---|---| +| `subscription_id` | The subscription's UUID | +| `subscriber_id` | The developer who owns the subscription | +| `status` | `ACTIVE` or `INACTIVE` | +| `subscription_plan.ref_id` | The plan's control-plane reference id, or `null` | +| `subscription_plan.name` | The plan's display name, or `null` | +| `api.name`, `api.version`, `api.type` | The artifact subscribed to. `api.type` is `Mcp` for an MCP server | +| `api.ref_id` | The artifact's gateway reference, or `""` | + +```json +{ + "subscription_id": "b2c3…", + "subscriber_id": "user-42", + "status": "ACTIVE", + "subscription_plan": { "ref_id": "gold-ref", "name": "Gold" }, + "api": { "name": "Catalog API", "version": "1.0.0", "ref_id": "catalog-ref", "type": "RestApi" } +} +``` + +The five events differ only in when they fire and what they add: + +| Event | Fired when | Extra fields | Encrypted field | +|---|---|---|---| +| `subscription.created` | A developer subscribes to an API or MCP server |—| `token` | +| `subscription.updated` | The status changes—suspend or resume |—|—| +| `subscription.plan_changed` | The plan changes in place | `previous_plan.ref_id`, `previous_plan.name` |—| +| `subscription.token_regenerated` | The subscription token is regenerated |—| `token` | +| `subscription.deleted` | A developer unsubscribes |—|—| + +The `token` field carries the subscription token, which callers send in the `Subscription-Key` header. On `token_regenerated` it is the **new** token; the previous one is already invalid. + +## API key events + +The `data` fields, and which events carry each: + +| Field | Present on | Notes | +|---|---|---| +| `key_id` | All | The key's UUID | +| `handle` | All | The key's URL identifier | +| `display_name` | All | The name the developer gave it | +| `expires_at` | `generated`, `regenerated` | ISO 8601, or `null` when the key never expires | +| `api` | All | `{ name, version, ref_id, type }`, as above | +| `subscription` | When the key came from a subscription | Omitted otherwise | +| `application` | When the key is associated with an application | `{ id, display_name, handle }`. Omitted on `generated`/`regenerated` when unassociated | + +```json +{ + "key_id": "c3d4…", + "handle": "my-prod-key", + "display_name": "my-prod-key", + "expires_at": "2027-01-31T00:00:00.000Z", + "api": { "name": "Catalog API", "version": "1.0.0", "ref_id": "catalog-ref", "type": "RestApi" }, + "application": { "id": "a1b2…", "display_name": "MyApp-Production", "handle": "myapp-production" } +} +``` + +Four events cover the key lifecycle: + +| Event | Fired when | Encrypted field | +|---|---|---| +| `apikey.generated` | A key is generated | `key` | +| `apikey.regenerated` | A key is rotated | `key` | +| `apikey.revoked` | A key is revoked. No `expires_at` or `application` |—| +| `apikey.application_updated` | A key's application association changes |—| + +On `apikey.application_updated`, `application` is the new association, or **`null`** when the association was cleared—including when the application it belonged to was deleted. Generating a key with an application set fires `apikey.generated` **and** an `apikey.application_updated`. + +## Application events + +These events carry the application's identity and description: + +| Field | Notes | +|---|---| +| `application_id` | The application's UUID | +| `display_name` | Its name | +| `handle` | Its URL identifier | +| `description` | Present on `created` and `updated` | +| `type` | Always the literal `"web"` on `created` and `updated`. The portal has no application-type concept, so don't branch on it | + +```json +{ + "application_id": "a1b2…", + "display_name": "MyApp-Production", + "handle": "myapp-production", + "description": "Production client for the storefront", + "type": "web" +} +``` + +Three events cover an application's lifecycle: + +| Event | Fired when | Fields | +|---|---|---| +| `application.created` | A developer creates an application | All of the above | +| `application.updated` | Its name or description changes | All of the above | +| `application.deleted` | An application is deleted | `application_id`, `display_name`, `handle` | + +Deleting an application also fires one `apikey.application_updated` per key that was associated with it, each with `application: null`. The keys themselves stay valid—see [Manage Applications](../consume-an-api/manage-applications.md). + +## Related + +- [Webhook Integration](../admin-settings/webhook-integration.md): register a subscriber and pick its events +- [Webhook Subscribers](../rest-api/webhook-subscribers.md): manage subscribers through the Management API +- [Webhook Events](../rest-api/webhook-events.md): read delivery history through the Management API +- [Manage API Keys](../consume-an-api/manage-api-keys.md): the lifecycle behind the `apikey.*` events +- [Manage Subscriptions](../consume-an-api/manage-subscriptions.md): the lifecycle behind the `subscription.*` events diff --git a/en/docs/api-portal/next/rest-api/api-content.md b/en/docs/api-portal/next/rest-api/api-content.md new file mode 100644 index 000000000..6bd072353 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/api-content.md @@ -0,0 +1,446 @@ +--- +title: "API Content" +description: "Upload, replace, get, and delete an API's landing page content and documentation via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/api-content/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/api-content.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# API Content + +## Upload API content + +<a id="opIdcreateApiContent"></a> + +`POST /apis/{apiId}/assets` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/apis/{apiId}/assets \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' \ + -F 'content=@content.zip' \ + -F 'docMetadata=[{"name":"External guide","url":"https://example.com/docs/guide","type":"LINK"}]' \ + -F 'imageMetadata={"api-icon":"icon.png"}' + +``` + +Uploads the static content package for an API. + +The `content` ZIP must contain at least one of these root directories: +- `web/` for API landing-page assets such as markdown, HTML, CSS, JavaScript, and images. +- `docs/` for downloadable API documents. + +Use `docMetadata` to add external document links that are stored alongside uploaded documents. +Use `imageMetadata` to map uploaded images to API image roles such as the API icon. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_content:create`, `dp:api_content:manage` + +</aside> + +<h3 id="upload-api-content-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|API content ZIP upload.| +|» content|body|string(binary)|true|ZIP upload field named `content`.| +|» docMetadata|body|string|false|Optional JSON string containing API document link metadata.| +|» imageMetadata|body|string|false|Optional JSON string containing API image metadata.| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +#### Detailed descriptions + +**body**: API content ZIP upload. + +Expected ZIP structure: +- `web/`: optional API landing-page files and images. +- `docs/`: optional downloadable documents. + +At least one of `web/` or `docs/` must exist at the ZIP root. +`docMetadata` and `imageMetadata` are JSON strings because they are submitted as multipart form fields. + +> Example responses +> +> 201 Response + +```json +{ + "message": "string" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="upload-api-content-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="upload-api-content-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Replace API content + +<a id="opIdreplaceApiContent"></a> + +`PUT /apis/{apiId}/assets` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/apis/{apiId}/assets \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' \ + -F 'content=@content.zip' \ + -F 'docMetadata=[{"name":"External guide","url":"https://example.com/docs/guide","type":"LINK"}]' \ + -F 'imageMetadata={"api-icon":"icon.png"}' + +``` + +Replaces or adds static content files for an existing API. + +The upload format is the same as `POST /api/v0.9/apis/{apiId}/assets`. +Existing files with the same stored `type` and `fileName` are updated; new files are created. +Image metadata is updated only when image metadata can be resolved from the upload or request body. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_content:update`, `dp:api_content:manage` + +</aside> + +<h3 id="replace-api-content-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|API content ZIP upload.| +|» content|body|string(binary)|true|ZIP upload field named `content`.| +|» docMetadata|body|string|false|Optional JSON string containing API document link metadata.| +|» imageMetadata|body|string|false|Optional JSON string containing API image metadata.| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +#### Detailed descriptions + +**body**: API content ZIP upload. + +Expected ZIP structure: +- `web/`: optional API landing-page files and images. +- `docs/`: optional downloadable documents. + +At least one of `web/` or `docs/` must exist at the ZIP root. +`docMetadata` and `imageMetadata` are JSON strings because they are submitted as multipart form fields. + +> Example responses +> +> 201 Response + +```json +{ + "message": "string" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="replace-api-content-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="replace-api-content-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Get an API content file + +<a id="opIdgetApiContentFile"></a> + +`GET /apis/{apiId}/assets` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/apis/{apiId}/assets?type=document&fileName=getting-started.md \ + -u {username}:{password} \ + -H 'Accept: text/markdown' + +``` + +Retrieves a single stored API content file. + +The `type` query parameter selects the stored content category and `fileName` selects the file +within that category. Text files and external document links are returned as text. Image files are +returned as binary content with a media type derived from the file extension. + +Image files (`type=IMAGE`) are publicly readable so that an API's icon renders on the public +listing and landing pages without a session; the organization is then this instance's own +configured one (mirrors `GET /views/{viewId}/asset`). All other content categories require a +session: an anonymous request for a non-image type is rejected. + +<h3 id="get-an-api-content-file-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|type|query|string|true|Stored API content type selector. Common values are `web`, `document`, `image`, and `link`, depending on how the uploaded ZIP content was classified.| +|fileName|query|string|true|Stored API content file name to retrieve.| +|orgId|query|string|false|Deprecated and ignored. Accepted only so existing callers (the portal's own image-URL rewrite appends it) are not rejected. The organization is always this instance's own — from the session when there is one, otherwise from `organization.handle` configuration. It was previously honoured on this unauthenticated endpoint, which made it a selector for any organization's API icons in a shared database.| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +> Example responses +> +> 200 Response + +``` +"<section>API overview</section>" +``` + +``` +"https://example.com/docs/guide" +``` + +```json +{ + "title": "API overview" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +``` +"string" +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-an-api-content-file-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Stored API content asset. The concrete media type depends on the stored file extension or whether the content is an external document link.|string| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Plain text success response.|string| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="get-an-api-content-file-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Delete API content files + +<a id="opIddeleteApiContentFile"></a> + +`DELETE /apis/{apiId}/assets` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/apis/{apiId}/assets?type=document \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes stored API content. + +Send both `type` and `fileName` to delete one file. Send only `type` to delete all stored content +files matching that content category for the API. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_content:delete`, `dp:api_content:manage` + +</aside> + +<h3 id="delete-api-content-files-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|type|query|string|true|Stored API content type selector. Common values are `web`, `document`, `image`, and `link`, depending on how the uploaded ZIP content was classified.| +|fileName|query|string|false|File name selector used to delete a single stored API content file.| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +> Example responses +> +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +``` +"string" +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="delete-api-content-files-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|API content deleted successfully.|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Plain text success response.|string| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="delete-api-content-files-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| diff --git a/en/docs/api-portal/next/rest-api/api-keys.md b/en/docs/api-portal/next/rest-api/api-keys.md new file mode 100644 index 000000000..55d6c1f19 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/api-keys.md @@ -0,0 +1,959 @@ +--- +title: "API Keys" +description: "Generate, list, regenerate, revoke, and associate API keys via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/api-keys/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/api-keys.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# API Keys + +## List all API keys for the current user + +<a id="opIdlistAllApiKeys"></a> + +`GET /api-keys` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists every API key created by the authenticated user across all APIs in the organization. Powers the API Portal's global "API Keys" page. Each item additionally carries the owning API's name, version, and type. Secret material is never returned. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_key:read`, `dp:api_key:manage` + +</aside> + +<h3 id="list-all-api-keys-for-the-current-user-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "weather_prod_key", + "displayName": "Weather Prod Key", + "apiId": "weather-api-v1", + "appId": "my-weather-app", + "appDisplayName": "My Mobile App", + "status": "ACTIVE", + "expiresAt": "2026-12-31T23:59:59Z", + "createdAt": "2019-08-24T14:15:22Z", + "revokedAt": "2019-08-24T14:15:22Z" + } + ], + "count": 1, + "pagination": { + "total": 42, + "limit": 20, + "offset": 0 + } +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-all-api-keys-for-the-current-user-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of API key metadata records.|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-all-api-keys-for-the-current-user-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[ApiKeyMetadataResponse](schemas.md#schemaapikeymetadataresponse)]|false|none|[API key metadata returned by list operations. Secret material is omitted.]| +|»» id|string|false|none|none| +|»» displayName|string|false|none|none| +|»» apiId|string|false|none|API ID the key belongs to.| +|»» appId|string¦null|false|none|ID of the application this key is associated with, if any. Analytics attribution only.| +|»» appDisplayName|string¦null|false|none|Display name of the associated application, if any.| +|»» status|string|false|none|none| +|»» expiresAt|string(date-time)¦null|false|none|none| +|»» createdAt|string(date-time)|false|none|none| +|»» revokedAt|string(date-time)¦null|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|ACTIVE| +|status|REVOKED| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Generate an API key + +<a id="opIdgenerateApiKey"></a> + +`POST /apis/{apiId}/api-keys/generate` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/apis/{apiId}/api-keys/generate \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Generates an API key stored in the API Portal (devportal is source of truth). The plaintext secret is returned once in the response and never persisted. A `apikey.generated` webhook event is published to the organization's configured webhook subscribers so they can register the key (e.g. with a gateway). Key `id` is optional — a UUID handle is generated when it is omitted; when provided it must match `^[a-z0-9][a-z0-9_-]{0,127}$`. `expiresAt` must include a timezone when sent as an ISO-8601 string. + +> Payload + +```json +{ + "id": "weather_prod_key", + "expiresAt": "2026-12-31T23:59:59Z" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_key:create`, `dp:api_key:manage` + +</aside> + +<h3 id="generate-an-api-key-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[ApiKeyRequest](schemas.md#schemaapikeyrequest)|true|API key payload. `id` must be lowercase and may contain numbers, underscores, and hyphens. `displayName` is an optional human-readable label that defaults to `id` when omitted. `expiresAt` can be an ISO-8601 datetime with timezone, epoch seconds, or epoch milliseconds. The parent resource (API or MCP server, depending on the path) is identified by the corresponding path parameter.| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +> Example responses +> +> 201 Response + +```json +{ + "id": "weather_prod_key", + "displayName": "Weather Prod Key", + "key": "ak_dGhpcyBpcyBub3QgYSByZWFsIGtleQ", + "expiresAt": "2026-12-31T23:59:59Z", + "status": "ACTIVE" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="generate-an-api-key-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Generated API key. The plaintext `key` is returned exactly once.|[ApiKeyResponse](schemas.md#schemaapikeyresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="generate-an-api-key-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the generated API key resource.| + +## List API keys + +<a id="opIdlistApiKeys"></a> + +`GET /apis/{apiId}/api-keys` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/apis/{apiId}/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists API keys for the given API. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_key:read`, `dp:api_key:manage` + +</aside> + +<h3 id="list-api-keys-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|appId|query|string|false|Optional application ID used to filter API keys associated with that application.| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "weather_prod_key", + "displayName": "Weather Prod Key", + "apiId": "weather-api-v1", + "appId": "my-weather-app", + "appDisplayName": "My Mobile App", + "status": "ACTIVE", + "expiresAt": "2026-12-31T23:59:59Z", + "createdAt": "2019-08-24T14:15:22Z", + "revokedAt": "2019-08-24T14:15:22Z" + } + ], + "count": 1, + "pagination": { + "total": 42, + "limit": 20, + "offset": 0 + } +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-api-keys-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of API key metadata records.|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-api-keys-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[ApiKeyMetadataResponse](schemas.md#schemaapikeymetadataresponse)]|false|none|[API key metadata returned by list operations. Secret material is omitted.]| +|»» id|string|false|none|none| +|»» displayName|string|false|none|none| +|»» apiId|string|false|none|API ID the key belongs to.| +|»» appId|string¦null|false|none|ID of the application this key is associated with, if any. Analytics attribution only.| +|»» appDisplayName|string¦null|false|none|Display name of the associated application, if any.| +|»» status|string|false|none|none| +|»» expiresAt|string(date-time)¦null|false|none|none| +|»» createdAt|string(date-time)|false|none|none| +|»» revokedAt|string(date-time)¦null|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|ACTIVE| +|status|REVOKED| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Regenerate an API key + +<a id="opIdregenerateApiKey"></a> + +`POST /apis/{apiId}/api-keys/regenerate` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/apis/{apiId}/api-keys/regenerate \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Regenerates the secret for an existing API key identified by `keyId` in the request body. An `apikey.regenerated` webhook event is published to the organization's configured webhook subscribers so they can invalidate the old secret (e.g. at a gateway). The new plaintext secret is returned once and never persisted. + +> Payload + +```json +{ + "keyId": "weather_prod_key", + "expiresAt": "2027-01-01T00:00:00Z" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_key:update`, `dp:api_key:manage` + +</aside> + +<h3 id="regenerate-an-api-key-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|Identifies the API key to regenerate by its `keyId`. `expiresAt` is optional and, if provided, updates the key's expiry; the key's `id`/`displayName` cannot be changed by this operation.| +|» keyId|body|string|true|The key's handle — the `id` returned by generate or list.| +|» expiresAt|body|any|false|New expiry for the key. The value can be an ISO-8601 datetime with timezone, epoch seconds, or epoch milliseconds. Omit to leave the current expiry unchanged.| +|»» *anonymous*|body|string(date-time)|false|none| +|»» *anonymous*|body|number|false|none| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "weather_prod_key", + "displayName": "Weather Prod Key", + "key": "ak_dGhpcyBpcyBub3QgYSByZWFsIGtleQ", + "expiresAt": "2026-12-31T23:59:59Z", + "status": "ACTIVE" +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Cannot regenerate a revoked key" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="regenerate-an-api-key-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Generated or regenerated API key. The plaintext `key` is returned exactly once.|[ApiKeyResponse](schemas.md#schemaapikeyresponse)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The key has already been revoked and cannot be regenerated.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Revoke an API key + +<a id="opIdrevokeApiKey"></a> + +`POST /apis/{apiId}/api-keys/revoke` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/apis/{apiId}/api-keys/revoke \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Revokes an existing API key identified by `keyId` in the request body. An `apikey.revoked` webhook event is published to the organization's configured webhook subscribers so they can immediately reject requests carrying the key (e.g. at a gateway). + +> Payload + +```json +{ + "keyId": "weather_prod_key" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_key:revoke`, `dp:api_key:manage` + +</aside> + +<h3 id="revoke-an-api-key-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|Identifies the API key to revoke by its `keyId`.| +|» keyId|body|string|true|The key's handle — the `id` returned by generate or list.| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +> Example responses +> +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Key already revoked or not found" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="revoke-an-api-key-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|API key revoked successfully.|None| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The key has already been revoked.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Associate an API key with an application + +<a id="opIdassociateApiKeyApplication"></a> + +`POST /apis/{apiId}/api-keys/associate` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/apis/{apiId}/api-keys/associate \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Associates (or re-associates) an existing API key with an application, for analytics attribution only — it has no effect on the key's validity or authorization. An `apikey.application_updated` webhook event is published once for this key, with a payload of `{ key_id, application }`. + +> Payload + +```json +{ + "keyId": "weather_prod_key", + "appId": "my-weather-app" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_key:update`, `dp:api_key:manage` + +</aside> + +<h3 id="associate-an-api-key-with-an-application-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|Identifies the API key and the application to associate it with.| +|» keyId|body|string|true|The key's handle — the `id` returned by generate or list.| +|» appId|body|string|true|API Portal application ID to associate the key with.| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +> Example responses +> +> 200 Response + +```json +{ + "application": { + "id": "my-weather-app", + "displayName": "My Mobile App" + } +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Cannot associate a revoked key" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="associate-an-api-key-with-an-application-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Association updated.|[ApiKeyApplicationResponse](schemas.md#schemaapikeyapplicationresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The key has already been revoked and cannot be associated with an application.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="associate-an-api-key-with-an-application-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Remove an API key's application association + +<a id="opIdremoveApiKeyApplication"></a> + +`POST /apis/{apiId}/api-keys/dissociate` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/apis/{apiId}/api-keys/dissociate \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Removes the application association from an API key identified by `keyId` in the request body, if any. An `apikey.application_updated` webhook event is published once for this key, with `application` set to `null`. + +> Payload + +```json +{ + "keyId": "weather_prod_key" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_key:update`, `dp:api_key:manage` + +</aside> + +<h3 id="remove-an-api-key's-application-association-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|Identifies the API key to remove the application association from.| +|» keyId|body|string|true|The key's handle — the `id` returned by generate or list.| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +> Example responses +> +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="remove-an-api-key's-application-association-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Association removed (or none existed).|None| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## List API keys associated with an application + +<a id="opIdlistApplicationApiKeys"></a> + +`GET /applications/{applicationId}/api-keys` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/applications/{applicationId}/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists all API keys (across every API) currently associated with the given application. Unlike `listApiKeys`, no `apiId` filter is required. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_key:read`, `dp:api_key:manage` + +</aside> + +<h3 id="list-api-keys-associated-with-an-application-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| +|applicationId|path|string|true|The application's handle (unique per org).| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "weather_prod_key", + "displayName": "Weather Prod Key", + "apiId": "weather-api-v1", + "appId": "my-weather-app", + "appDisplayName": "My Mobile App", + "status": "ACTIVE", + "expiresAt": "2026-12-31T23:59:59Z", + "createdAt": "2019-08-24T14:15:22Z", + "revokedAt": "2019-08-24T14:15:22Z" + } + ], + "count": 1, + "pagination": { + "total": 42, + "limit": 20, + "offset": 0 + } +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-api-keys-associated-with-an-application-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of API key metadata records.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-api-keys-associated-with-an-application-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[ApiKeyMetadataResponse](schemas.md#schemaapikeymetadataresponse)]|false|none|[API key metadata returned by list operations. Secret material is omitted.]| +|»» id|string|false|none|none| +|»» displayName|string|false|none|none| +|»» apiId|string|false|none|API ID the key belongs to.| +|»» appId|string¦null|false|none|ID of the application this key is associated with, if any. Analytics attribution only.| +|»» appDisplayName|string¦null|false|none|Display name of the associated application, if any.| +|»» status|string|false|none|none| +|»» expiresAt|string(date-time)¦null|false|none|none| +|»» createdAt|string(date-time)|false|none|none| +|»» revokedAt|string(date-time)¦null|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|ACTIVE| +|status|REVOKED| diff --git a/en/docs/api-portal/next/rest-api/api-workflows.md b/en/docs/api-portal/next/rest-api/api-workflows.md new file mode 100644 index 000000000..c8a44b055 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/api-workflows.md @@ -0,0 +1,481 @@ +--- +title: "API Workflows" +description: "Create, list, get, update, delete, and generate an agent prompt for API workflows via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/api-workflows/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/api-workflows.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# API Workflows + +## Create an API workflow + +<a id="opIdcreateApiWorkflow"></a> + +`POST /views/{viewId}/api-workflows` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/views/{viewId}/api-workflows \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates an API workflow in the selected view. If `id` is omitted, the service generates one from the display name. `ARAZZO` content is parsed from JSON or YAML; invalid Arazzo content returns a bad request. + +> Payload + +```json +{ + "displayName": "Weather onboarding", + "id": "weather-onboarding", + "description": "Guides users through the Weather API onboarding workflow.", + "contentType": "ARAZZO", + "apiWorkflowDefinition": { + "arazzo": "1.0.1", + "info": { + "title": "Weather onboarding", + "version": "1.0.0" + }, + "workflows": [] + } +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_workflow:create`, `dp:api_workflow:manage` + +</aside> + +<h3 id="create-an-api-workflow-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[APIWorkflowCreateRequest](schemas.md#schemaapiworkflowcreaterequest)|true|API workflow creation payload. Use `contentType` `ARAZZO` for JSON/YAML workflow content or `MD` for Markdown workflow content.| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| + +> Example responses +> +> 201 Response + +```json +{ + "id": "workflow-12345", + "displayName": "Weather onboarding", + "status": "PUBLISHED" +} +``` + +> 400 Response + +```json +{ + "message": "string" +} +``` + +<h3 id="create-an-api-workflow-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Created API workflow summary.|[APIWorkflowCreateResponse](schemas.md#schemaapiworkflowcreateresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| + +## List API workflows + +<a id="opIdgetAllApiWorkflows"></a> + +`GET /views/{viewId}/api-workflows` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/views/{viewId}/api-workflows \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists all API workflows for the selected organization view. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_workflow:read`, `dp:api_workflow:manage` + +</aside> + +<h3 id="list-api-workflows-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "workflow-12345", + "displayName": "Weather onboarding", + "description": "string", + "agentPrompt": "string", + "status": "PUBLISHED", + "agentVisibility": "VISIBLE", + "contentType": "ARAZZO", + "apiWorkflowDefinition": "string", + "markdownContent": "string", + "createdAt": "May 7, 2026", + "updatedAt": "string", + "createdBy": "string", + "updatedBy": "string" + } + ], + "count": 1, + "pagination": { + "total": 42, + "limit": 20, + "offset": 0 + } +} +``` + +<h3 id="list-api-workflows-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of API workflow DTOs.|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| + +<h3 id="list-api-workflows-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[APIWorkflowResponse](schemas.md#schemaapiworkflowresponse)]|false|none|none| +|»» id|string|false|none|The workflow's handle (unique per org and view). Not the internal database uuid.| +|»» displayName|string|false|none|none| +|»» description|string|false|none|none| +|»» agentPrompt|string|false|none|none| +|»» status|string|false|none|none| +|»» agentVisibility|string|false|none|none| +|»» contentType|string|false|none|none| +|»» apiWorkflowDefinition|string¦null|false|none|none| +|»» markdownContent|string¦null|false|none|none| +|»» createdAt|string|false|none|none| +|»» updatedAt|string¦null|false|none|none| +|»» createdBy|string¦null|false|none|none| +|»» updatedBy|string¦null|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|DRAFT| +|status|PUBLISHED| +|agentVisibility|VISIBLE| +|agentVisibility|HIDDEN| +|contentType|ARAZZO| +|contentType|MD| + +## Get an API workflow + +<a id="opIdgetApiWorkflow"></a> + +`GET /views/{viewId}/api-workflows/{apiWorkflowId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/views/{viewId}/api-workflows/{apiWorkflowId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a single API workflow by ID from the selected view. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_workflow:read`, `dp:api_workflow:manage` + +</aside> + +<h3 id="get-an-api-workflow-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| +|apiWorkflowId|path|string|true|The API workflow's handle (unique per org and view).| + +> Example responses +> +> 200 Response + +```json +{ + "id": "workflow-12345", + "displayName": "Weather onboarding", + "description": "Guides users through the Weather API onboarding workflow.", + "agentPrompt": "Follow this workflow to onboard a Weather API user.", + "status": "PUBLISHED", + "agentVisibility": "VISIBLE", + "contentType": "ARAZZO", + "apiWorkflowDefinition": "{\"arazzo\":\"1.0.1\",\"info\":{\"title\":\"Weather onboarding\",\"version\":\"1.0.0\"},\"workflows\":[]}", + "markdownContent": null, + "createdAt": "May 7, 2026", + "updatedAt": "2026-05-07T08:30:00Z" +} +``` + +> 404 Response + +```json +{ + "message": "string" +} +``` + +<h3 id="get-an-api-workflow-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|API workflow DTO.|[APIWorkflowResponse](schemas.md#schemaapiworkflowresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| + +## Update an API workflow + +<a id="opIdupdateApiWorkflow"></a> + +`PUT /views/{viewId}/api-workflows/{apiWorkflowId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/views/{viewId}/api-workflows/{apiWorkflowId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates API workflow metadata and content for the selected view. Duplicate handles return a conflict. + +> Payload + +```json +{ + "displayName": "Weather onboarding v2", + "id": "weather-onboarding-v2", + "description": "Updated Weather API onboarding workflow.", + "agentPrompt": "string", + "status": "PUBLISHED", + "agentVisibility": "VISIBLE", + "contentType": "ARAZZO", + "apiWorkflowDefinition": {}, + "markdownContent": "string" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_workflow:update`, `dp:api_workflow:manage` + +</aside> + +<h3 id="update-an-api-workflow-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[APIWorkflowUpdateRequest](schemas.md#schemaapiworkflowupdaterequest)|true|API workflow update payload. Include only the fields that should change.| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| +|apiWorkflowId|path|string|true|The API workflow's handle (unique per org and view).| + +> Example responses +> +> 200 Response + +```json +{ + "message": "string" +} +``` + +<h3 id="update-an-api-workflow-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| + +## Delete an API workflow + +<a id="opIddeleteApiWorkflow"></a> + +`DELETE /views/{viewId}/api-workflows/{apiWorkflowId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/views/{viewId}/api-workflows/{apiWorkflowId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes an API workflow from the selected view. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_workflow:delete`, `dp:api_workflow:manage` + +</aside> + +<h3 id="delete-an-api-workflow-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| +|apiWorkflowId|path|string|true|The API workflow's handle (unique per org and view).| + +> Example responses +> +> 200 Response + +```json +{ + "message": "string" +} +``` + +<h3 id="delete-an-api-workflow-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| + +## Generate an API workflow agent prompt + +<a id="opIdgeneratePrompt"></a> + +`POST /views/{viewId}/api-workflows/generate-prompt` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/views/{viewId}/api-workflows/generate-prompt \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Generates the default agent prompt text for a proposed API workflow using the supplied name, description, APIs, and view context. + +> Payload + +```json +{ + "displayName": "Weather onboarding", + "description": "Guides users through the Weather API onboarding workflow.", + "apis": [ + {} + ], + "orgHandle": "acme", + "viewName": "default", + "id": "weather-onboarding" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api_workflow:create`, `dp:api_workflow:manage` + +</aside> + +<h3 id="generate-an-api-workflow-agent-prompt-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[APIWorkflowPromptRequest](schemas.md#schemaapiworkflowpromptrequest)|true|API workflow prompt-generation payload.| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "agentPrompt": "You are an API workflow assistant. Help the user complete Weather onboarding." +} +``` + +> 500 Response + +```json +{ + "message": "string" +} +``` + +<h3 id="generate-an-api-workflow-agent-prompt-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Generated API workflow agent prompt.|[APIWorkflowPromptResponse](schemas.md#schemaapiworkflowpromptresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| diff --git a/en/docs/api-portal/next/rest-api/apis.md b/en/docs/api-portal/next/rest-api/apis.md new file mode 100644 index 000000000..a29d43101 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/apis.md @@ -0,0 +1,784 @@ +--- +title: "APIs" +description: "Create, list, get, update, and delete API metadata via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/apis/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/apis.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# APIs + +## Create API metadata + +<a id="opIdcreateApiMetadata"></a> + +`POST /apis` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/apis \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' \ + -F 'definition=@definition.yaml' \ + -F 'artifact=@artifact.zip' \ + -F 'metadata={"name":"Weather API","version":"v1","description":"Weather forecast API","type":"REST","agentVisibility":"VISIBLE", "status":"PUBLISHED","tags":["weather"],"labels":["default"],"endPoints":{ "productionURL":"https://api.example.com/weather", "sandboxURL":"https://sandbox.example.com/weather"},"subscriptionPlans":[{"id":"Gold"}]}' + +``` + +Creates API metadata from either a full API artifact ZIP, an API metadata YAML file (`api.yaml` / `metadata.yaml` / `mcp.yaml`), or a `metadata` JSON string. An API definition file is required unless supplied by the artifact ZIP. The YAML `spec` block accepts: `displayName`, `version`, `description`, `type`, `status`, `agentVisibility`, `tags`, `labels`, `referenceId`, `endpoints` (sandboxUrl, productionUrl), `businessInformation` (owners), and `subscriptionPlans`. The service also stores labels, subscription plan mappings, image metadata, and schema definitions for GraphQL APIs when provided. Via the JSON `metadata` field, `type` is required — an omitted type is rejected with `400` (via YAML, an omitted `spec.type` defaults to `REST`). MCP servers must be created via `POST /api/v0.9/mcp-servers` instead — a request whose resolved `type` is `MCP` is rejected with `400`. +`subscriptionPlans` links existing org-level plans to this API by name — it does not create plans. In YAML it is a string array (`["Gold", "Silver"]`). In the JSON `metadata` field it is an object array where only `id` is used (`[{"id":"Gold"}]`); extra fields such as `planId`, `displayName`, or `requestCount` are ignored. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api:create`, `dp:api:manage` + +</aside> + +<h3 id="create-api-metadata-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|API metadata upload. Send either `artifact`, or `metadata` with `definition`. For a GraphQL API the `definition` field carries the SDL schema. (MCP servers are created via `/mcp-servers` with the dedicated `McpServerMultipartBody`, not this body.)| +|» definition|body|string(binary)|false|API definition file. For REST/SOAP/etc. this is the OpenAPI/WSDL/AsyncAPI contract; for a GraphQL API it is the SDL schema.| +|» artifact|body|string(binary)|false|Full API ZIP artifact containing metadata and definition files.| +|» metadata|body|string|false|API metadata. Supply it in either of two forms:| + +#### Detailed descriptions + +**» metadata**: API metadata. Supply it in either of two forms: + +- A JSON string field. +- An uploaded YAML or JSON file — a Kubernetes-style document with `kind`, + `metadata.name`, and a `spec` block. Accepted file names are + `metadata.yaml`, `.yml`, `.json`, `api.yaml`, or `mcp.yaml`. + +As a JSON string, these top-level fields are read: `name`, `version`, +`description`, `type`, `agentVisibility`, `status`, `referenceId`, `id`, `tags`, +`labels`, `owners`, `endPoints` (`productionURL`, `sandboxURL`), and +`subscriptionPlans`. + +`subscriptionPlans` is an array of `{ id }` objects. Only `id` is read, and the +plan must already exist in the organization. + +The stored handle comes from `id`. When the API is created from a YAML artifact +instead, the handle always comes from `metadata.name`. + +> Example responses +> +> 201 Response + +```json +{ + "id": "weather-api-v1", + "refId": "cp-api-12345", + "name": "Weather API", + "title": "Weather Forecast API", + "version": "v1", + "status": "PUBLISHED", + "description": "Weather forecast API.", + "type": "REST", + "agentVisibility": "VISIBLE", + "tags": [ + "weather" + ], + "labels": [ + "default" + ], + "endPoints": { + "productionURL": "https://api.example.com/weather", + "sandboxURL": "https://sandbox.example.com/weather" + }, + "subscriptionPlans": [ + { + "id": "Gold" + } + ] +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="create-api-metadata-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Created API metadata payload returned by the service.|[ApiMetadataCreateResponse](schemas.md#schemaapimetadatacreateresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="create-api-metadata-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the created API metadata resource.| + +## List API metadata + +<a id="opIdgetAllApiMetadataForOrganization"></a> + +`GET /apis` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/apis \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists API metadata for an organization. The service supports exact filters by API name, version, and tags, free-text search with `query`, and view filtering. Unknown query parameters are rejected. MCP-typed records are never returned here — use `GET /api/v0.9/mcp-servers`. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api:read`, `dp:api:manage` + +</aside> + +<h3 id="list-api-metadata-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|query|query|string|false|Free-text API metadata search term.| +|name|query|string|false|Exact API name filter.| +|version|query|string|false|Exact API version filter.| +|tags|query|string|false|Comma-separated tag names. Matches APIs tagged with any of the given names.| +|view|query|string|false|API Portal view name used to filter visible APIs.| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "weather-api-v1", + "refId": "cp-api-12345", + "name": "Weather API", + "version": "v1", + "status": "PUBLISHED", + "description": "Weather forecast API.", + "type": "REST", + "agentVisibility": "VISIBLE", + "labels": [ + "default" + ], + "endPoints": { + "sandboxURL": "https://sandbox.example.com/weather", + "productionURL": "https://api.example.com/weather" + } + } + ], + "count": 1, + "pagination": { + "total": 1, + "limit": 20, + "offset": 0 + } +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-api-metadata-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of API metadata DTOs.|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-api-metadata-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[allOf]|false|none|none| + +*allOf* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»» *anonymous*|[ApiInfoResponse](schemas.md#schemaapiinforesponse)|false|none|Fields are returned at the root of ApiMetadataResponse / ApiMetadataCreateResponse (not nested under an `apiInfo` key) — this schema exists only to share the field set between the two via `allOf`.| +|»»» name|string|false|none|none| +|»»» title|string¦null|false|none|none| +|»»» remotes|[object]|false|none|none| +|»»» version|string|false|none|none| +|»»» status|string|false|none|API lifecycle status.| +|»»» description|string|false|none|none| +|»»» type|string|false|none|The stored/returned type constant (src/utils/constants.js API_TYPE) — distinct from the request-time keyword accepted on create/update (see `type` in ApiMetadataMultipartBody: REST, SOAP, MCP, WS, WEBSUB, GRAPHQL). REST maps to `RestApi` and WEBSUB maps to `WebSubApi`; the rest are returned unchanged.| +|»»» referenceId|string¦null|false|none|External reference ID. Present when the API was created from a YAML artifact whose `spec` block sets `referenceId` — the create response echoes the parsed YAML back.| +|»»» agentVisibility|string|false|none|none| +|»»» addedLabels|[string]|false|none|none| +|»»» removedLabels|[string]|false|none|none| +|»»» owners|[ApiOwnersResponse](schemas.md#schemaapiownersresponse)|false|none|none| +|»»»» technicalOwner|string|false|none|none| +|»»»» businessOwner|string|false|none|none| +|»»»» businessOwnerEmail|string|false|none|none| +|»»»» technicalOwnerEmail|string|false|none|none| +|»»» apiImageMetadata|[ApiImageMetadataResponse](schemas.md#schemaapiimagemetadataresponse)|false|none|none| +|»»»» **additionalProperties**|string|false|none|none| +|»»» tags|[string]|false|none|none| +|»»» labels|[string]|false|none|none| + +*and* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»» *anonymous*|object|false|none|none| +|»»» id|string|false|none|The API's handle (unique per org). Not the internal database uuid.| +|»»» refId|string¦null|false|none|Platform API (Control Plane) reference ID for this API. Used for MCP registry visibility filtering and included in outbound webhook event payloads. Null/absent for APIs that exist only in the API Portal and are not registered with the Platform API — e.g. MCP servers published via the registry.| +|»»» dataSource|string¦null|false|none|Indicates which content matched the search term: `METADATA` if the match was in the API's own metadata, or a content type (e.g. a value from the API Content `type` field) if the match was inside an uploaded content file. Only computed by getAllApiMetadataForOrganization when both the `query` search parameter is supplied and the database is PostgreSQL — absent on SQLite (the dev default) and absent from every other operation (get/create/update single API).| +|»»» planId|string|false|none|none| +|»»» endPoints|[ApiEndpointsResponse](schemas.md#schemaapiendpointsresponse)|false|none|none| +|»»»» sandboxURL|string|false|none|none| +|»»»» productionURL|string|false|none|none| +|»»» subscriptionPlans|[[SubscriptionPlanResponse](schemas.md#schemasubscriptionplanresponse)]|false|none|none| +|»»»» id|string|false|none|The plan's handle (unique per org). Not the internal database uuid.| +|»»»» displayName|string|false|none|none| +|»»»» description|string|false|none|none| +|»»»» limits|[object]|false|none|Rate/quota limits enforced for this plan. Empty when the plan is unlimited.| +|»»»»» limitType|string|false|none|none| +|»»»»» limitCount|any|false|none|Returned as a string when the stored count exceeds the safe integer range, otherwise a number. Unlimited plans have no limit entries — the `limits` array is empty.| + +*oneOf* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»»»»»» *anonymous*|integer|false|none|none| + +*xor* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»»»»»» *anonymous*|string|false|none|none| + +*continued* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»»»»» timeUnit|string¦null|false|none|none| +|»»»»» timeAmount|integer|false|none|none| +|»»»» refId|string¦null|false|none|Platform API subscription plan UUID associated with this plan.| +|»»»» orgId|string|false|none|none| +|»»»» createdBy|string|false|none|Identity of the user who created this subscription plan, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|»»»» updatedBy|string|false|none|Identity of the user who last updated this subscription plan, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|»»»» createdAt|string(date-time)|false|none|none| +|»»»» updatedAt|string(date-time)|false|none|none| +|»»» createdBy|string|false|none|Identity of the user who created this API, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|»»» updatedBy|string|false|none|Identity of the user who last updated this API, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|»»» createdAt|string(date-time)|false|none|none| +|»»» updatedAt|string(date-time)|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|PUBLISHED| +|status|DEPRECATED| +|type|RestApi| +|type|SOAP| +|type|Mcp| +|type|WS| +|type|WebSubApi| +|type|GRAPHQL| +|agentVisibility|VISIBLE| +|agentVisibility|HIDDEN| +|limitType|REQUEST_COUNT| +|limitType|EVENT_COUNT| +|limitType|BANDWIDTH| +|limitType|TOTAL_TOKEN_COUNT| +|timeUnit|MINUTE| +|timeUnit|HOUR| +|timeUnit|DAY| +|timeUnit|MONTH| +|timeUnit|null| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Get API metadata + +<a id="opIdgetApiMetadata"></a> + +`GET /apis/{apiId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/apis/{apiId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a single API metadata record by API ID. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api:read`, `dp:api:manage` + +</aside> + +<h3 id="get-api-metadata-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "weather-api-v1", + "refId": "cp-api-12345", + "name": "Weather API", + "title": "Weather Forecast API", + "remotes": [], + "version": "v1", + "status": "PUBLISHED", + "description": "Weather forecast API.", + "type": "REST", + "agentVisibility": "VISIBLE", + "labels": [ + "default" + ], + "endPoints": { + "sandboxURL": "https://sandbox.example.com/weather", + "productionURL": "https://api.example.com/weather" + }, + "subscriptionPlans": [ + { + "id": "Gold" + } + ], + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +``` +"string" +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-api-metadata-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|API metadata DTO returned by the service.|[ApiMetadataResponse](schemas.md#schemaapimetadataresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Plain text success response.|string| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="get-api-metadata-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Update API metadata + +<a id="opIdupdateApiMetadata"></a> + +`PUT /apis/{apiId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/apis/{apiId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' \ + -F 'definition=@definition.yaml' \ + -F 'artifact=@artifact.zip' \ + -F 'metadata={"name":"Weather API","version":"v1","description":"Weather forecast API","type":"REST","agentVisibility":"VISIBLE", "status":"PUBLISHED","tags":["weather"],"labels":["default"],"endPoints":{ "productionURL":"https://api.example.com/weather", "sandboxURL":"https://sandbox.example.com/weather"},"subscriptionPlans":[{"id":"Gold"}]}' + +``` + +Updates API metadata and its stored definition. Accepts the same YAML spec fields and `metadata` JSON format as the create operation. The update flow can also adjust label mappings, subscription plan mappings, schema definitions, and image metadata. Status changes to unpublished are rejected when active subscriptions exist. `type` is required (see the create operation) and is immutable — it must match the API's existing type; a different value is rejected with `409`. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api:update`, `dp:api:manage` + +</aside> + +<h3 id="update-api-metadata-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|API metadata upload. Send either `artifact`, or `metadata` with `definition`. For a GraphQL API the `definition` field carries the SDL schema. (MCP servers are created via `/mcp-servers` with the dedicated `McpServerMultipartBody`, not this body.)| +|» definition|body|string(binary)|false|API definition file. For REST/SOAP/etc. this is the OpenAPI/WSDL/AsyncAPI contract; for a GraphQL API it is the SDL schema.| +|» artifact|body|string(binary)|false|Full API ZIP artifact containing metadata and definition files.| +|» metadata|body|string|false|API metadata. Supply it in either of two forms:| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +#### Detailed descriptions + +**» metadata**: API metadata. Supply it in either of two forms: + +- A JSON string field. +- An uploaded YAML or JSON file — a Kubernetes-style document with `kind`, + `metadata.name`, and a `spec` block. Accepted file names are + `metadata.yaml`, `.yml`, `.json`, `api.yaml`, or `mcp.yaml`. + +As a JSON string, these top-level fields are read: `name`, `version`, +`description`, `type`, `agentVisibility`, `status`, `referenceId`, `id`, `tags`, +`labels`, `owners`, `endPoints` (`productionURL`, `sandboxURL`), and +`subscriptionPlans`. + +`subscriptionPlans` is an array of `{ id }` objects. Only `id` is read, and the +plan must already exist in the organization. + +The stored handle comes from `id`. When the API is created from a YAML artifact +instead, the handle always comes from `metadata.name`. + +> Example responses +> +> 200 Response + +```json +{ + "id": "weather-api-v1", + "refId": "cp-api-12345", + "name": "Weather API", + "title": "Weather Forecast API", + "remotes": [], + "version": "v1", + "status": "PUBLISHED", + "description": "Weather forecast API.", + "type": "REST", + "agentVisibility": "VISIBLE", + "labels": [ + "default" + ], + "endPoints": { + "sandboxURL": "https://sandbox.example.com/weather", + "productionURL": "https://api.example.com/weather" + }, + "subscriptionPlans": [ + { + "id": "Gold" + } + ], + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="update-api-metadata-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|API metadata DTO returned by the service.|[ApiMetadataResponse](schemas.md#schemaapimetadataresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="update-api-metadata-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Delete API metadata + +<a id="opIddeleteApiMetadata"></a> + +`DELETE /apis/{apiId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/apis/{apiId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: text/plain' + +``` + +Deletes API metadata when the API has no active subscriptions. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:api:delete`, `dp:api:manage` + +</aside> + +<h3 id="delete-api-metadata-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|apiId|path|string|true|The API's handle (unique per org). Resolves only to REST, SOAP, WebSocket, WebSub, and GraphQL APIs. Model Context Protocol (MCP) servers are addressed via `/mcp-servers`.| + +> Example responses +> +> 200 Response + +``` +"string" +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="delete-api-metadata-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Plain text success response.|string| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="delete-api-metadata-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| diff --git a/en/docs/api-portal/next/rest-api/application-keys.md b/en/docs/api-portal/next/rest-api/application-keys.md new file mode 100644 index 000000000..9ff84b235 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/application-keys.md @@ -0,0 +1,329 @@ +--- +title: "Application Keys" +description: "Map an OAuth client ID, generate an access token, and remove a client ID mapping via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/application-keys/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/application-keys.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Application Keys + +## Map an OAuth `client_id` to an API Portal application + +<a id="opIdgenerateApplicationKeys"></a> + +`POST /applications/{applicationId}/generate-keys` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/applications/{applicationId}/generate-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Maps an OAuth client_id — created directly in the selected key manager — to the specified application. The portal does not create or register OAuth clients; it only stores the client_id reference and later proxies token requests for it. + +> Payload + +```json +{ + "keyManager": "Resident Key Manager", + "type": "PRODUCTION", + "consumerKey": "consumer-key-123" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:application_key:create`, `dp:application_key:manage` + +</aside> + +<h3 id="map-an-oauth-client_id-to-an-api-portal-application-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[AppKeyMappingRequest](schemas.md#schemaappkeymappingrequest)|true|Maps an OAuth client_id — created directly in the key manager — to this application. The application is identified by the `applicationId` path parameter.| +|applicationId|path|string|true|The application's handle (unique per org).| + +> Example responses +> +> 200 Response + +```json +{ + "keyMappingId": "km-12345", + "keyManager": "Resident Key Manager", + "type": "PRODUCTION", + "consumerKey": "consumer-key-123", + "tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="map-an-oauth-client_id-to-an-api-portal-application-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Application OAuth key mapping response.|[ApplicationOAuthKeyResponse](schemas.md#schemaapplicationoauthkeyresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="map-an-oauth-client_id-to-an-api-portal-application-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Generate an OAuth access token + +<a id="opIdgenerateOAuthKeys"></a> + +`POST /applications/{applicationId}/oauth-keys/{keyMappingId}/generate-token` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/applications/{applicationId}/oauth-keys/{keyMappingId}/generate-token \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Generates an access token for an existing application OAuth key mapping. The portal calls the Authorization Server token endpoint directly using the client appKeyMappings supplied in `consumerSecret`. + +> Payload + +```json +{ + "consumerSecret": "my-consumer-secret", + "scopes": [ + "weather.read" + ], + "validityPeriod": 3600 +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:application_key:create`, `dp:application_key:manage` + +</aside> + +<h3 id="generate-an-oauth-access-token-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[OAuthGenerateTokenRequest](schemas.md#schemaoauthgeneratetokenrequest)|false|OAuth token generation payload. The portal calls the Authorization Server token endpoint directly.| +|applicationId|path|string|true|The application's handle (unique per org).| +|keyMappingId|path|string|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.example", + "validityTime": 3600, + "tokenScopes": [ + "weather.read" + ] +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="generate-an-oauth-access-token-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|OAuth access token response.|[OAuthTokenResponse](schemas.md#schemaoauthtokenresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="generate-an-oauth-access-token-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Remove an OAuth `client_id` mapping + +<a id="opIdrevokeOAuthKeys"></a> + +`DELETE /applications/{applicationId}/oauth-keys/{keyMappingId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/applications/{applicationId}/oauth-keys/{keyMappingId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Removes the local client_id mapping for an application. This does not affect the OAuth client in the key manager — that client is owned and managed externally. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:application_key:revoke` + +</aside> + +<h3 id="remove-an-oauth-client_id-mapping-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|The application's handle (unique per org).| +|keyMappingId|path|string|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "message": "Operation completed successfully" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="remove-an-oauth-client_id-mapping-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Message or generic response payload.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="remove-an-oauth-client_id-mapping-responseschema">Response schema</h3> diff --git a/en/docs/api-portal/next/rest-api/applications.md b/en/docs/api-portal/next/rest-api/applications.md new file mode 100644 index 000000000..05696d1b2 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/applications.md @@ -0,0 +1,531 @@ +--- +title: "Applications" +description: "List, create, get, update, and delete applications via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/applications/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/applications.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Applications + +## List applications for the authenticated user + +<a id="opIdlistApplications"></a> + +`GET /applications` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/applications \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns all applications owned by the authenticated user in the specified organization. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:application:read`, `dp:application:manage` + +</aside> + +<h3 id="list-applications-for-the-authenticated-user-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "my-weather-app", + "displayName": "Weather App", + "description": "Application used to call Weather APIs.", + "appKeyMappings": [ + { + "asClientId": "asgardeo-client-abc123", + "kmId": "km-uuid-12345", + "type": "PRODUCTION" + } + ] + } + ], + "count": 1, + "pagination": { + "total": 1, + "limit": 20, + "offset": 0 + } +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-applications-for-the-authenticated-user-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of application DTOs.|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-applications-for-the-authenticated-user-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[ApplicationResponse](schemas.md#schemaapplicationresponse)]|false|none|none| +|»» id|string|false|none|The application's handle (unique per org). Not the internal database uuid.| +|»» displayName|string|false|none|none| +|»» description|string|false|none|none| +|»» appKeyMappings|[[ApplicationKeyMappingSummary](schemas.md#schemaapplicationkeymappingsummary)]|false|none|[OAuth client ID mapping entry attached to an application.]| +|»»» asClientId|string|false|none|OAuth client ID, created directly in the key manager and linked to this application.| +|»»» kmId|string|false|none|UUID of the key manager this client ID is linked to.| +|»»» type|string|false|none|Key type for this mapping.| +|»» createdBy|string|false|none|Identity of the user who created this application, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|»» updatedBy|string|false|none|Identity of the user who last updated this application, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|»» createdAt|string(date-time)|false|none|none| +|»» updatedAt|string(date-time)|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|type|PRODUCTION| +|type|SANDBOX| + +## Create an application + +<a id="opIdsaveApplication"></a> + +`POST /applications` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/applications \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates an API Portal application in the specified organization. The request may be JSON, multipart form fields, or an application YAML file in the `application` multipart field. An `application.created` webhook event is published to the organization's configured webhook subscribers. + +> Payload + +```json +{ + "displayName": "Weather App", + "id": "my-weather-app", + "description": "Application used to call Weather APIs." +} +``` + +```yaml +displayName: Weather App +id: my-weather-app +description: Application used to call Weather APIs. + +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:application:create`, `dp:application:manage` + +</aside> + +<h3 id="create-an-application-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[ApplicationRequest](schemas.md#schemaapplicationrequest)|true|Application payload. Send JSON, multipart form fields, or an application YAML file in the `application` field. The JSON example below (`displayName`, `id`, `description`) applies only to the `application/json` content type. When an application YAML **file** is uploaded instead, its content must use the nested shape `metadata.name` (handle) and `spec.displayName` / `spec.description` — any top-level `id` inside that YAML file is ignored.| + +> Example responses +> +> 201 Response + +```json +{ + "id": "my-weather-app", + "displayName": "Weather App", + "description": "Application used to call Weather APIs.", + "appKeyMappings": [] +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="create-an-application-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Application DTO.|[ApplicationResponse](schemas.md#schemaapplicationresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="create-an-application-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the created application.| + +## Get an application + +<a id="opIdgetApplication"></a> + +`GET /applications/{applicationId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/applications/{applicationId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns the details of a single application owned by the authenticated user. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:application:read`, `dp:application:manage` + +</aside> + +<h3 id="get-an-application-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|The application's handle (unique per org).| + +> Example responses +> +> 200 Response + +```json +{ + "id": "my-weather-app", + "displayName": "Weather App", + "description": "Application used to call Weather APIs.", + "appKeyMappings": [], + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-an-application-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Application DTO.|[ApplicationResponse](schemas.md#schemaapplicationresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Update an application + +<a id="opIdupdateApplication"></a> + +`PUT /applications/{applicationId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/applications/{applicationId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates an application owned by the authenticated user in the specified organization. The request may be JSON, multipart form fields, or an application YAML file in the `application` multipart field. An `application.updated` webhook event is published. + +> Payload + +```json +{ + "displayName": "Weather App", + "id": "my-weather-app", + "description": "Application used to call Weather APIs." +} +``` + +```yaml +displayName: Weather App +id: my-weather-app +description: Application used to call Weather APIs. + +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:application:update`, `dp:application:manage` + +</aside> + +<h3 id="update-an-application-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[ApplicationRequest](schemas.md#schemaapplicationrequest)|true|Application payload. Send JSON, multipart form fields, or an application YAML file in the `application` field. The JSON example below (`displayName`, `id`, `description`) applies only to the `application/json` content type. When an application YAML **file** is uploaded instead, its content must use the nested shape `metadata.name` (handle) and `spec.displayName` / `spec.description` — any top-level `id` inside that YAML file is ignored.| +|applicationId|path|string|true|The application's handle (unique per org).| + +> Example responses +> +> 200 Response + +```json +{ + "id": "my-weather-app", + "displayName": "Weather App", + "description": "Application used to call Weather APIs.", + "appKeyMappings": [], + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="update-an-application-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Application DTO.|[ApplicationResponse](schemas.md#schemaapplicationresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="update-an-application-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Delete an application + +<a id="opIddeleteApplication"></a> + +`DELETE /applications/{applicationId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/applications/{applicationId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: text/plain' + +``` + +Deletes an application owned by the authenticated user. Before removing the application record the service will make a best-effort attempt to revoke registered OAuth clients with their respective key managers and deletes all stored key mappings; failures are logged as warnings and do not abort deletion. An `application.deleted` webhook event is published, plus one `apikey.application_updated` event (with a cleared association) per API key that was associated with the application. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:application:delete`, `dp:application:manage` + +</aside> + +<h3 id="delete-an-application-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|The application's handle (unique per org).| + +> Example responses +> +> 200 Response + +``` +"string" +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="delete-an-application-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Plain text success response.|string| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| diff --git a/en/docs/api-portal/next/rest-api/authentication.md b/en/docs/api-portal/next/rest-api/authentication.md new file mode 100644 index 000000000..97308dc38 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/authentication.md @@ -0,0 +1,112 @@ +--- +title: "Authenticate to the API Portal REST API" +description: "OAuth2/OIDC scopes and API key authentication for the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/authentication/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/authentication.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Authenticate to the API Portal REST API + +- oAuth2 authentication. OAuth2/OIDC access token with fine-grained API Portal scopes. Each operation declares the exact resource/action scope it requires. + + - Flow: authorizationCode + - Authorization URL = [https://localhost:9443/oauth2/authorize](https://localhost:9443/oauth2/authorize) + - Token URL = [https://localhost:9443/oauth2/token](https://localhost:9443/oauth2/token) + +|Scope|Scope Description| +|---|---| +|dp:organization:read|Read organizations.| +|dp:organization:create|Create organizations.| +|dp:organization:update|Update organizations.| +|dp:organization:delete|Delete organizations.| +|dp:organization:manage|Manage organizations (including creating, updating, and deleting).| +|dp:organization_content:read|Read organization theme assets.| +|dp:organization_content:manage|Apply or reset organization theme.| +|dp:api:read|Read API metadata.| +|dp:api:create|Create API metadata.| +|dp:api:update|Update API metadata.| +|dp:api:delete|Delete API metadata.| +|dp:api:manage|Manage API metadata.| +|dp:api_content:read|Read API content.| +|dp:api_content:create|Create API content.| +|dp:api_content:update|Update API content.| +|dp:api_content:delete|Delete API content.| +|dp:api_content:manage|Manage API content.| +|dp:mcp_server:read|Read MCP server metadata.| +|dp:mcp_server:create|Create MCP server metadata.| +|dp:mcp_server:update|Update MCP server metadata.| +|dp:mcp_server:delete|Delete MCP server metadata.| +|dp:mcp_server:manage|Manage MCP server metadata.| +|dp:mcp_server_content:read|Read MCP server content.| +|dp:mcp_server_content:create|Create MCP server content.| +|dp:mcp_server_content:update|Update MCP server content.| +|dp:mcp_server_content:delete|Delete MCP server content.| +|dp:mcp_server_content:manage|Manage MCP server content.| +|dp:mcp_server_key:read|Read MCP server API keys.| +|dp:mcp_server_key:create|Generate MCP server API keys.| +|dp:mcp_server_key:update|Regenerate MCP server API keys.| +|dp:mcp_server_key:revoke|Revoke MCP server API keys.| +|dp:mcp_server_key:manage|Manage MCP server API keys.| +|dp:subscription_plan:read|Read subscription plans.| +|dp:subscription_plan:create|Create subscription plans.| +|dp:subscription_plan:update|Update subscription plans.| +|dp:subscription_plan:delete|Delete subscription plans.| +|dp:subscription_plan:manage|Manage subscription plans.| +|dp:label:read|Read labels.| +|dp:label:create|Create labels.| +|dp:label:update|Update labels.| +|dp:label:delete|Delete labels.| +|dp:label:manage|Manage labels.| +|dp:application:read|Read applications.| +|dp:application:create|Create applications.| +|dp:application:update|Update applications.| +|dp:application:delete|Delete applications.| +|dp:application:manage|Manage applications.| +|dp:subscription:read|Read subscriptions.| +|dp:subscription:create|Create subscriptions.| +|dp:subscription:update|Update subscriptions.| +|dp:subscription:delete|Delete subscriptions.| +|dp:subscription:manage|Manage subscriptions.| +|dp:api_key:read|Read API keys.| +|dp:api_key:create|Generate API keys.| +|dp:api_key:update|Regenerate API keys.| +|dp:api_key:revoke|Revoke API keys.| +|dp:api_key:manage|Manage API keys.| +|dp:application_key_mapping:read|Read application key mappings.| +|dp:application_key_mapping:create|Create application key mappings.| +|dp:application_key_mapping:manage|Manage application key mappings.| +|dp:view:read|Read views.| +|dp:view:create|Create views.| +|dp:view:update|Update views.| +|dp:view:delete|Delete views.| +|dp:view:manage|Manage views.| +|dp:application_key:create|Generate and create application keys.| +|dp:application_key:update|Update application keys.| +|dp:application_key:revoke|Revoke application keys.| +|dp:application_key:manage|Manage application keys.| +|dp:api_workflow:read|Read API workflows.| +|dp:api_workflow:create|Create or generate API workflows.| +|dp:api_workflow:update|Update API workflows.| +|dp:api_workflow:delete|Delete API workflows.| +|dp:api_workflow:manage|Manage API workflows.| +|dp:event:read|Read webhook events and delivery details.| +|dp:key_manager:read|Read key manager configurations.| +|dp:key_manager:create|Create key manager configurations.| +|dp:key_manager:update|Update key manager configurations.| +|dp:key_manager:delete|Delete key manager configurations.| +|dp:key_manager:manage|Manage key manager configurations (including creating, updating, and deleting).| +|dp:webhook_subscriber:read|Read webhook subscriber configurations.| +|dp:webhook_subscriber:create|Create webhook subscriber configurations.| +|dp:webhook_subscriber:update|Update webhook subscriber configurations.| +|dp:webhook_subscriber:delete|Delete webhook subscriber configurations.| +|dp:webhook_subscriber:manage|Manage webhook subscriber configurations (including creating, updating, and deleting).| + +* API Key (apiKeyAuth) + - Parameter Name: **x-api-key**, in: header. API key authentication. Server-side authorization should bind each key to the same fine-grained permissions used by OAuth2 scopes. diff --git a/en/docs/api-portal/next/rest-api/key-managers.md b/en/docs/api-portal/next/rest-api/key-managers.md new file mode 100644 index 000000000..48540a4a7 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/key-managers.md @@ -0,0 +1,545 @@ +--- +title: "Key Managers" +description: "Create, list, get, update, and delete key managers via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/key-managers/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/key-managers.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Key Managers + +## Create a key manager + +<a id="opIdcreateKeyManager"></a> + +`POST /key-managers` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/key-managers \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates a key manager configuration for the organization. If `id` is omitted, the service generates a UUID handle. Accepts either a `application/json` body or a `multipart/form-data` upload with a `keymanager` field containing the KeyManager YAML file. OAuth applications are created directly in the key manager itself, outside the portal — the portal only needs the token endpoint to proxy `client_appKeyMappings` token requests. + +> Payload + +```json +{ + "displayName": "Asgardeo", + "id": "asgardeo-prod", + "enabled": true, + "tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token" +} +``` + +```yaml +displayName: Asgardeo +id: asgardeo-prod +enabled: true +tokenEndpoint: https://api.asgardeo.io/t/myorg/oauth2/token + +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:key_manager:create`, `dp:key_manager:manage` + +</aside> + +<h3 id="create-a-key-manager-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[KeyManagerRequest](schemas.md#schemakeymanagerrequest)|false|Key manager configuration payload. Submit as `application/json` or as `multipart/form-data` with a `keymanager` field containing a KeyManager YAML file.| + +> Example responses +> +> 201 Response + +```json +{ + "id": "asgardeo-prod", + "displayName": "Asgardeo", + "orgId": "org-12345", + "enabled": true, + "tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="create-a-key-manager-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Key manager configuration response.|[KeyManagerResponseSchema](schemas.md#schemakeymanagerresponseschema)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="create-a-key-manager-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the created key manager.| + +## List key managers + +<a id="opIdgetKeyManagers"></a> + +`GET /key-managers` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/key-managers \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns key manager configurations for the organization. Admins receive the full configuration for every key manager, including disabled ones; other callers receive the minimal, developer-facing view of enabled key managers only, with no admin credentials. Admin appKeyMappings are never included in the response. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:key_manager:read`, `dp:application_key_mapping:read` + +</aside> + +<h3 id="list-key-managers-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "asgardeo-prod", + "displayName": "Asgardeo", + "orgId": "org-12345", + "enabled": true, + "tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token", + "createdBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" + } + ], + "count": 1, + "pagination": { + "total": 42, + "limit": 20, + "offset": 0 + } +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-key-managers-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of key manager configurations. Admins receive KeyManagerResponseSchema items; other callers receive the minimal KeyManagerPublicResponseSchema items.|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-key-managers-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[anyOf]|false|none|none| + +*anyOf* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»» *anonymous*|[KeyManagerResponseSchema](schemas.md#schemakeymanagerresponseschema)|false|none|Key manager configuration.| +|»»» id|string|false|none|The key manager's handle (unique per org). Not the internal database uuid.| +|»»» displayName|string|false|none|none| +|»»» orgId|string|false|none|none| +|»»» enabled|boolean|false|none|none| +|»»» tokenEndpoint|string(uri)|false|none|none| +|»»» createdBy|string|false|none|Identity of the user who created this key manager, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|»»» updatedBy|string|false|none|Identity of the user who last updated this key manager, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|»»» createdAt|string(date-time)|false|none|none| +|»»» updatedAt|string(date-time)|false|none|none| + +*or* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»» *anonymous*|[KeyManagerPublicResponseSchema](schemas.md#schemakeymanagerpublicresponseschema)|false|none|Minimal developer-facing key manager view.| +|»»» id|string|false|none|The key manager's handle (unique per org). Not the internal database uuid.| +|»»» displayName|string|false|none|none| +|»»» tokenEndpoint|string(uri)|false|none|none| + +*continued* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +## Get a key manager + +<a id="opIdgetKeyManager"></a> + +`GET /key-managers/{kmId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/key-managers/{kmId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a single key manager configuration by ID. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:key_manager:read`, `dp:key_manager:manage` + +</aside> + +<h3 id="get-a-key-manager-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|kmId|path|string|true|The key manager's handle (its `id` in request/response payloads), not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "asgardeo-prod", + "displayName": "Asgardeo", + "orgId": "org-12345", + "enabled": true, + "tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-a-key-manager-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Key manager configuration response.|[KeyManagerResponseSchema](schemas.md#schemakeymanagerresponseschema)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Update a key manager + +<a id="opIdupdateKeyManager"></a> + +`PUT /key-managers/{kmId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/key-managers/{kmId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates an existing key manager configuration. Accepts either a `application/json` body or a `multipart/form-data` upload with a `keymanager` YAML file. Only supplied fields are updated; omitted fields retain their stored values. + +> Payload + +```json +{ + "displayName": "Asgardeo", + "id": "asgardeo-prod", + "enabled": true, + "tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token" +} +``` + +```yaml +displayName: Asgardeo +id: asgardeo-prod +enabled: true +tokenEndpoint: https://api.asgardeo.io/t/myorg/oauth2/token + +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:key_manager:update`, `dp:key_manager:manage` + +</aside> + +<h3 id="update-a-key-manager-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[KeyManagerUpdateRequest](schemas.md#schemakeymanagerupdaterequest)|false|Key manager update payload. All fields are optional; only supplied fields are updated. Submit as `application/json` or as `multipart/form-data` with a `keymanager` field containing a KeyManager YAML file.| +|kmId|path|string|true|The key manager's handle (its `id` in request/response payloads), not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "asgardeo-prod", + "displayName": "Asgardeo", + "orgId": "org-12345", + "enabled": true, + "tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="update-a-key-manager-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Key manager configuration response.|[KeyManagerResponseSchema](schemas.md#schemakeymanagerresponseschema)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="update-a-key-manager-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Delete a key manager + +<a id="opIddeleteKeyManager"></a> + +`DELETE /key-managers/{kmId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/key-managers/{kmId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes a key manager configuration by ID. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:key_manager:delete`, `dp:key_manager:manage` + +</aside> + +<h3 id="delete-a-key-manager-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|kmId|path|string|true|The key manager's handle (its `id` in request/response payloads), not the internal database uuid.| + +> Example responses +> +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="delete-a-key-manager-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Key manager deleted successfully.|None| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| diff --git a/en/docs/api-portal/next/rest-api/labels.md b/en/docs/api-portal/next/rest-api/labels.md new file mode 100644 index 000000000..ca3c7572a --- /dev/null +++ b/en/docs/api-portal/next/rest-api/labels.md @@ -0,0 +1,494 @@ +--- +title: "Labels" +description: "Create, list, update, and delete labels via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/labels/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/labels.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Labels + +## Create a label + +<a id="opIdcreateLabel"></a> + +`POST /labels` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/labels \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates a label for the organization. + +> Payload + +```json +{ + "id": "premium", + "displayName": "Premium APIs" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:label:create`, `dp:label:manage` + +</aside> + +<h3 id="create-a-label-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[LabelRequest](schemas.md#schemalabelrequest)|true|Label payload.| + +> Example responses +> +> 201 Response + +```json +{ + "id": "premium", + "displayName": "Premium APIs" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="create-a-label-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|The created label.|[LabelResponse](schemas.md#schemalabelresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="create-a-label-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the created label.| + +## List labels + +<a id="opIdlistLabels"></a> + +`GET /labels` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/labels \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns all labels configured for the organization. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:label:read`, `dp:label:manage` + +</aside> + +<h3 id="list-labels-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "premium", + "displayName": "Premium APIs" + } + ], + "count": 1, + "pagination": { + "total": 42, + "limit": 20, + "offset": 0 + } +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-labels-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Paginated list of label DTOs.|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-labels-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[LabelResponse](schemas.md#schemalabelresponse)]|false|none|none| +|»» id|string|false|none|The label's handle (unique per org). Not the internal database uuid.| +|»» displayName|string|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Get a label + +<a id="opIdgetLabel"></a> + +`GET /labels/{labelId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/labels/{labelId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a single label by handle. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:label:read`, `dp:label:manage` + +</aside> + +<h3 id="get-a-label-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|labelId|path|string|true|The label's handle (its `id` in request/response payloads), not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "premium", + "displayName": "Premium APIs" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-a-label-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Label DTO.|[LabelResponse](schemas.md#schemalabelresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Update a label + +<a id="opIdupdateLabel"></a> + +`PUT /labels/{labelId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/labels/{labelId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates an existing label by handle. + +> Payload + +```json +{ + "id": "premium", + "displayName": "Premium APIs" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:label:update`, `dp:label:manage` + +</aside> + +<h3 id="update-a-label-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|labelId|path|string|true|The label's handle (its `id` in request/response payloads), not the internal database uuid.| +|body|body|[LabelRequest](schemas.md#schemalabelrequest)|true|Label payload.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "premium", + "displayName": "Premium APIs" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="update-a-label-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Label DTO.|[LabelResponse](schemas.md#schemalabelresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="update-a-label-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Delete a label + +<a id="opIddeleteLabel"></a> + +`DELETE /labels/{labelId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/labels/{labelId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes a label by handle. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:label:delete`, `dp:label:manage` + +</aside> + +<h3 id="delete-a-label-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|labelId|path|string|true|The label's handle (its `id` in request/response payloads), not the internal database uuid.| + +> Example responses +> +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="delete-a-label-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Label deleted successfully.|None| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| diff --git a/en/docs/api-portal/next/rest-api/mcp-server-content.md b/en/docs/api-portal/next/rest-api/mcp-server-content.md new file mode 100644 index 000000000..490683be2 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/mcp-server-content.md @@ -0,0 +1,431 @@ +--- +title: "MCP Server Content" +description: "Upload, replace, get, and delete an MCP server's content via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/mcp-server-content/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/mcp-server-content.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# MCP Server Content + +## Upload MCP server content + +<a id="opIdcreateMcpServerContent"></a> + +`POST /mcp-servers/{mcpServerId}/assets` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId}/assets \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' \ + -F 'content=@content.zip' \ + -F 'docMetadata=[{"name":"External guide","url":"https://example.com/docs/guide","type":"LINK"}]' \ + -F 'imageMetadata={"api-icon":"icon.png"}' + +``` + +Uploads the static content package for an MCP server. Mirrors `POST /api/v0.9/apis/{apiId}/assets`. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server_content:create`, `dp:mcp_server_content:manage` + +</aside> + +<h3 id="upload-mcp-server-content-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|API content ZIP upload.| +|» content|body|string(binary)|true|ZIP upload field named `content`.| +|» docMetadata|body|string|false|Optional JSON string containing API document link metadata.| +|» imageMetadata|body|string|false|Optional JSON string containing API image metadata.| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +#### Detailed descriptions + +**body**: API content ZIP upload. + +Expected ZIP structure: +- `web/`: optional API landing-page files and images. +- `docs/`: optional downloadable documents. + +At least one of `web/` or `docs/` must exist at the ZIP root. +`docMetadata` and `imageMetadata` are JSON strings because they are submitted as multipart form fields. + +> Example responses +> +> 201 Response + +```json +{ + "message": "string" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="upload-mcp-server-content-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="upload-mcp-server-content-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Replace MCP server content + +<a id="opIdreplaceMcpServerContent"></a> + +`PUT /mcp-servers/{mcpServerId}/assets` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId}/assets \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' \ + -F 'content=@content.zip' \ + -F 'docMetadata=[{"name":"External guide","url":"https://example.com/docs/guide","type":"LINK"}]' \ + -F 'imageMetadata={"api-icon":"icon.png"}' + +``` + +Replaces or adds static content files for an existing MCP server. Mirrors `PUT /api/v0.9/apis/{apiId}/assets`. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server_content:update`, `dp:mcp_server_content:manage` + +</aside> + +<h3 id="replace-mcp-server-content-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|API content ZIP upload.| +|» content|body|string(binary)|true|ZIP upload field named `content`.| +|» docMetadata|body|string|false|Optional JSON string containing API document link metadata.| +|» imageMetadata|body|string|false|Optional JSON string containing API image metadata.| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +#### Detailed descriptions + +**body**: API content ZIP upload. + +Expected ZIP structure: +- `web/`: optional API landing-page files and images. +- `docs/`: optional downloadable documents. + +At least one of `web/` or `docs/` must exist at the ZIP root. +`docMetadata` and `imageMetadata` are JSON strings because they are submitted as multipart form fields. + +> Example responses +> +> 201 Response + +```json +{ + "message": "string" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="replace-mcp-server-content-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="replace-mcp-server-content-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Get an MCP server content file + +<a id="opIdgetMcpServerContentFile"></a> + +`GET /mcp-servers/{mcpServerId}/assets` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId}/assets?type=document&fileName=getting-started.md \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: text/markdown' + +``` + +Retrieves a single stored MCP server content file. Mirrors `GET /api/v0.9/apis/{apiId}/assets`. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server_content:read`, `dp:mcp_server_content:manage` + +</aside> + +<h3 id="get-an-mcp-server-content-file-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|type|query|string|true|Stored API content type selector. Common values are `web`, `document`, `image`, and `link`, depending on how the uploaded ZIP content was classified.| +|fileName|query|string|true|Stored API content file name to retrieve.| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +> Example responses +> +> 200 Response + +``` +"<section>API overview</section>" +``` + +``` +"https://example.com/docs/guide" +``` + +```json +{ + "title": "API overview" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +``` +"string" +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-an-mcp-server-content-file-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Stored API content asset. The concrete media type depends on the stored file extension or whether the content is an external document link.|string| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Plain text success response.|string| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="get-an-mcp-server-content-file-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Delete MCP server content files + +<a id="opIddeleteMcpServerContentFile"></a> + +`DELETE /mcp-servers/{mcpServerId}/assets` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId}/assets?type=document \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes stored MCP server content. Mirrors `DELETE /api/v0.9/apis/{apiId}/assets`. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server_content:delete`, `dp:mcp_server_content:manage` + +</aside> + +<h3 id="delete-mcp-server-content-files-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|type|query|string|true|Stored API content type selector. Common values are `web`, `document`, `image`, and `link`, depending on how the uploaded ZIP content was classified.| +|fileName|query|string|false|File name selector used to delete a single stored API content file.| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +> Example responses +> +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +``` +"string" +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="delete-mcp-server-content-files-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|MCP server content deleted successfully.|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Plain text success response.|string| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="delete-mcp-server-content-files-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| diff --git a/en/docs/api-portal/next/rest-api/mcp-server-keys.md b/en/docs/api-portal/next/rest-api/mcp-server-keys.md new file mode 100644 index 000000000..06df61945 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/mcp-server-keys.md @@ -0,0 +1,708 @@ +--- +title: "MCP Server Keys" +description: "Generate, list, regenerate, revoke, and associate MCP server API keys via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/mcp-server-keys/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/mcp-server-keys.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# MCP Server Keys + +## Generate an MCP server API key + +<a id="opIdgenerateMcpServerApiKey"></a> + +`POST /mcp-servers/{mcpServerId}/api-keys/generate` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId}/api-keys/generate \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Generates an API key for an MCP server. Mirrors `POST /api/v0.9/apis/{apiId}/api-keys/generate`. + +> Payload + +```json +{ + "id": "weather_prod_key", + "expiresAt": "2026-12-31T23:59:59Z" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server_key:create`, `dp:mcp_server_key:manage` + +</aside> + +<h3 id="generate-an-mcp-server-api-key-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[ApiKeyRequest](schemas.md#schemaapikeyrequest)|true|API key payload. `id` must be lowercase and may contain numbers, underscores, and hyphens. `displayName` is an optional human-readable label that defaults to `id` when omitted. `expiresAt` can be an ISO-8601 datetime with timezone, epoch seconds, or epoch milliseconds. The parent resource (API or MCP server, depending on the path) is identified by the corresponding path parameter.| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +> Example responses +> +> 201 Response + +```json +{ + "id": "weather_prod_key", + "displayName": "Weather Prod Key", + "key": "ak_dGhpcyBpcyBub3QgYSByZWFsIGtleQ", + "expiresAt": "2026-12-31T23:59:59Z", + "status": "ACTIVE" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="generate-an-mcp-server-api-key-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Generated API key. The plaintext `key` is returned exactly once.|[ApiKeyResponse](schemas.md#schemaapikeyresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="generate-an-mcp-server-api-key-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the generated API key resource.| + +## List MCP server API keys + +<a id="opIdlistMcpServerApiKeys"></a> + +`GET /mcp-servers/{mcpServerId}/api-keys` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId}/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists API keys for the given MCP server. Mirrors `GET /api/v0.9/apis/{apiId}/api-keys`. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server_key:read`, `dp:mcp_server_key:manage` + +</aside> + +<h3 id="list-mcp-server-api-keys-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|appId|query|string|false|Optional application ID used to filter API keys associated with that application.| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "weather_prod_key", + "displayName": "Weather Prod Key", + "apiId": "weather-api-v1", + "appId": "my-weather-app", + "appDisplayName": "My Mobile App", + "status": "ACTIVE", + "expiresAt": "2026-12-31T23:59:59Z", + "createdAt": "2019-08-24T14:15:22Z", + "revokedAt": "2019-08-24T14:15:22Z" + } + ], + "count": 1, + "pagination": { + "total": 42, + "limit": 20, + "offset": 0 + } +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-mcp-server-api-keys-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of API key metadata records.|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-mcp-server-api-keys-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[ApiKeyMetadataResponse](schemas.md#schemaapikeymetadataresponse)]|false|none|[API key metadata returned by list operations. Secret material is omitted.]| +|»» id|string|false|none|none| +|»» displayName|string|false|none|none| +|»» apiId|string|false|none|API ID the key belongs to.| +|»» appId|string¦null|false|none|ID of the application this key is associated with, if any. Analytics attribution only.| +|»» appDisplayName|string¦null|false|none|Display name of the associated application, if any.| +|»» status|string|false|none|none| +|»» expiresAt|string(date-time)¦null|false|none|none| +|»» createdAt|string(date-time)|false|none|none| +|»» revokedAt|string(date-time)¦null|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|ACTIVE| +|status|REVOKED| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Regenerate an MCP server API key + +<a id="opIdregenerateMcpServerApiKey"></a> + +`POST /mcp-servers/{mcpServerId}/api-keys/regenerate` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId}/api-keys/regenerate \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Regenerates the secret for an existing MCP server API key identified by `keyId` in the request body. Mirrors `POST /api/v0.9/apis/{apiId}/api-keys/regenerate`. + +> Payload + +```json +{ + "keyId": "weather_prod_key", + "expiresAt": "2027-01-01T00:00:00Z" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server_key:update`, `dp:mcp_server_key:manage` + +</aside> + +<h3 id="regenerate-an-mcp-server-api-key-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|Identifies the API key to regenerate by its `keyId`. `expiresAt` is optional and, if provided, updates the key's expiry; the key's `id`/`displayName` cannot be changed by this operation.| +|» keyId|body|string|true|The key's handle — the `id` returned by generate or list.| +|» expiresAt|body|any|false|New expiry for the key. The value can be an ISO-8601 datetime with timezone, epoch seconds, or epoch milliseconds. Omit to leave the current expiry unchanged.| +|»» *anonymous*|body|string(date-time)|false|none| +|»» *anonymous*|body|number|false|none| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +> Example responses +> +> 200 Response + +```json +{ + "id": "weather_prod_key", + "displayName": "Weather Prod Key", + "key": "ak_dGhpcyBpcyBub3QgYSByZWFsIGtleQ", + "expiresAt": "2026-12-31T23:59:59Z", + "status": "ACTIVE" +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Cannot regenerate a revoked key" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="regenerate-an-mcp-server-api-key-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Generated or regenerated API key. The plaintext `key` is returned exactly once.|[ApiKeyResponse](schemas.md#schemaapikeyresponse)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The key has already been revoked and cannot be regenerated.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Revoke an MCP server API key + +<a id="opIdrevokeMcpServerApiKey"></a> + +`POST /mcp-servers/{mcpServerId}/api-keys/revoke` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId}/api-keys/revoke \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Revokes an existing MCP server API key identified by `keyId` in the request body. Mirrors `POST /api/v0.9/apis/{apiId}/api-keys/revoke`. + +> Payload + +```json +{ + "keyId": "weather_prod_key" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server_key:revoke`, `dp:mcp_server_key:manage` + +</aside> + +<h3 id="revoke-an-mcp-server-api-key-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|Identifies the API key to revoke by its `keyId`.| +|» keyId|body|string|true|The key's handle — the `id` returned by generate or list.| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +> Example responses +> +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Key already revoked or not found" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="revoke-an-mcp-server-api-key-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|API key revoked successfully.|None| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The key has already been revoked.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Associate an MCP server API key with an application + +<a id="opIdassociateMcpServerApiKeyApplication"></a> + +`POST /mcp-servers/{mcpServerId}/api-keys/associate` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId}/api-keys/associate \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Associates (or re-associates) an existing MCP server API key with an application, for analytics attribution only. Mirrors `POST /api/v0.9/apis/{apiId}/api-keys/associate`. + +> Payload + +```json +{ + "keyId": "weather_prod_key", + "appId": "my-weather-app" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server_key:update`, `dp:mcp_server_key:manage` + +</aside> + +<h3 id="associate-an-mcp-server-api-key-with-an-application-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|Identifies the API key and the application to associate it with.| +|» keyId|body|string|true|The key's handle — the `id` returned by generate or list.| +|» appId|body|string|true|API Portal application ID to associate the key with.| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +> Example responses +> +> 200 Response + +```json +{ + "application": { + "id": "my-weather-app", + "displayName": "My Mobile App" + } +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Cannot associate a revoked key" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="associate-an-mcp-server-api-key-with-an-application-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Association updated.|[ApiKeyApplicationResponse](schemas.md#schemaapikeyapplicationresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The key has already been revoked and cannot be associated with an application.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="associate-an-mcp-server-api-key-with-an-application-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Remove an MCP server API key's application association + +<a id="opIdremoveMcpServerApiKeyApplication"></a> + +`POST /mcp-servers/{mcpServerId}/api-keys/dissociate` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId}/api-keys/dissociate \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Removes the application association from an MCP server API key identified by `keyId` in the request body, if any. Mirrors `POST /api/v0.9/apis/{apiId}/api-keys/dissociate`. + +> Payload + +```json +{ + "keyId": "weather_prod_key" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server_key:update`, `dp:mcp_server_key:manage` + +</aside> + +<h3 id="remove-an-mcp-server-api-key's-application-association-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|Identifies the API key to remove the application association from.| +|» keyId|body|string|true|The key's handle — the `id` returned by generate or list.| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +> Example responses +> +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="remove-an-mcp-server-api-key's-application-association-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Association removed (or none existed).|None| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| diff --git a/en/docs/api-portal/next/rest-api/mcp-servers.md b/en/docs/api-portal/next/rest-api/mcp-servers.md new file mode 100644 index 000000000..734492e98 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/mcp-servers.md @@ -0,0 +1,735 @@ +--- +title: "MCP Servers" +description: "Create, list, get, update, and delete MCP server metadata via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/mcp-servers/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/mcp-servers.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# MCP Servers + +## Create MCP server metadata + +<a id="opIdcreateMcpServer"></a> + +`POST /mcp-servers` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/mcp-servers \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' \ + -F 'artifact=@artifact.zip' \ + -F 'definition=@definition.yaml' \ + -F 'metadata={"name":"Travel Assistant MCP","version":"v1","description":"MCP server for travel planning tools","type":"MCP","agentVisibility":"VISIBLE","status":"PUBLISHED", "tags":["mcp"],"labels":["default"],"endPoints":{"productionURL":"https://mcp.example.com", "sandboxURL":"https://mcp.example.com"},"subscriptionPlans":[{"id":"Gold"}]}' + +``` + +Creates API Portal MCP server metadata. Accepts the same metadata input formats as `POST /api/v0.9/apis` (artifact ZIP, `api.yaml` / `mcp.yaml`, or `metadata` JSON), but the created record is always typed `MCP`. An MCP server's contract is its `definition` (tools schema) — the tools, resources, and prompts it exposes — not an OpenAPI-style API contract; a `definition` is required. Via the JSON `metadata` field, `type` must be explicitly `MCP`; an omitted type or any other value is rejected with a 400 (use `POST /api/v0.9/apis`). + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server:create`, `dp:mcp_server:manage` + +</aside> + +<h3 id="create-mcp-server-metadata-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|MCP server upload. Provide the server metadata via `metadata` (a JSON string or an uploaded YAML/JSON file), or a full `artifact` ZIP, together with its `definition` — the tools, resources, and prompts the server exposes. An MCP server has no OpenAPI-style API contract; its `definition` (tools schema) IS its contract, so it is required on create (and replaces the stored schema when supplied on update).| +|» artifact|body|string(binary)|false|Full MCP server ZIP artifact containing the metadata and the definition.| +|» definition|body|string(binary)|false|MCP tools schema (YAML or JSON) listing the tools, resources, and prompts the server exposes. Required on create; when supplied on update it replaces the stored schema.| +|» metadata|body|string|false|MCP server metadata, supplied either as a JSON string field or as an uploaded YAML/JSON file (a Kubernetes-style document of kind `MCP`; file names `metadata.yaml`/`.yml`/`.json`, or `api.yaml`/`mcp.yaml`). As a JSON string it accepts these top-level fields: `name`, `version`, `description`, `type` (must be `MCP`), `agentVisibility`, `status`, `referenceId`, `id`, `tags`, `labels`, `owners`, `endPoints` (productionURL, sandboxURL), and `subscriptionPlans` (array of `{ id }` objects — only `id` is read; the plan must already exist in the organization). `id` becomes the MCP server's stored handle.| + +> Example responses +> +> 201 Response + +```json +{ + "id": "weather-api-v1", + "refId": "cp-api-12345", + "name": "Weather API", + "title": "Weather Forecast API", + "version": "v1", + "status": "PUBLISHED", + "description": "Weather forecast API.", + "type": "MCP", + "agentVisibility": "VISIBLE", + "tags": [ + "weather" + ], + "labels": [ + "default" + ], + "endPoints": { + "productionURL": "https://api.example.com/weather", + "sandboxURL": "https://sandbox.example.com/weather" + }, + "subscriptionPlans": [ + { + "id": "Gold" + } + ] +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="create-mcp-server-metadata-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Created MCP server metadata payload returned by the service. Always typed `MCP`.|[ApiMetadataCreateResponse](schemas.md#schemaapimetadatacreateresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="create-mcp-server-metadata-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the created MCP server metadata resource.| + +## List MCP server metadata + +<a id="opIdgetAllMcpServersForOrganization"></a> + +`GET /mcp-servers` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/mcp-servers \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists MCP server metadata for an organization. Mirrors `GET /api/v0.9/apis` but only returns MCP-typed records. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server:read`, `dp:mcp_server:manage` + +</aside> + +<h3 id="list-mcp-server-metadata-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|query|query|string|false|Free-text API metadata search term.| +|name|query|string|false|Exact API name filter.| +|version|query|string|false|Exact API version filter.| +|tags|query|string|false|Comma-separated tag names. Matches APIs tagged with any of the given names.| +|view|query|string|false|API Portal view name used to filter visible APIs.| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "weather-api-v1", + "refId": "cp-api-12345", + "name": "Weather API", + "version": "v1", + "status": "PUBLISHED", + "description": "Weather forecast API.", + "type": "MCP", + "agentVisibility": "VISIBLE", + "labels": [ + "default" + ], + "endPoints": { + "sandboxURL": "https://sandbox.example.com/weather", + "productionURL": "https://api.example.com/weather" + } + } + ], + "count": 1, + "pagination": { + "total": 1, + "limit": 20, + "offset": 0 + } +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-mcp-server-metadata-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of MCP server metadata DTOs. Always typed `MCP`.|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-mcp-server-metadata-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[allOf]|false|none|none| + +*allOf* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»» *anonymous*|[ApiInfoResponse](schemas.md#schemaapiinforesponse)|false|none|Fields are returned at the root of ApiMetadataResponse / ApiMetadataCreateResponse (not nested under an `apiInfo` key) — this schema exists only to share the field set between the two via `allOf`.| +|»»» name|string|false|none|none| +|»»» title|string¦null|false|none|none| +|»»» remotes|[object]|false|none|none| +|»»» version|string|false|none|none| +|»»» status|string|false|none|API lifecycle status.| +|»»» description|string|false|none|none| +|»»» type|string|false|none|The stored/returned type constant (src/utils/constants.js API_TYPE) — distinct from the request-time keyword accepted on create/update (see `type` in ApiMetadataMultipartBody: REST, SOAP, MCP, WS, WEBSUB, GRAPHQL). REST maps to `RestApi` and WEBSUB maps to `WebSubApi`; the rest are returned unchanged.| +|»»» referenceId|string¦null|false|none|External reference ID. Present when the API was created from a YAML artifact whose `spec` block sets `referenceId` — the create response echoes the parsed YAML back.| +|»»» agentVisibility|string|false|none|none| +|»»» addedLabels|[string]|false|none|none| +|»»» removedLabels|[string]|false|none|none| +|»»» owners|[ApiOwnersResponse](schemas.md#schemaapiownersresponse)|false|none|none| +|»»»» technicalOwner|string|false|none|none| +|»»»» businessOwner|string|false|none|none| +|»»»» businessOwnerEmail|string|false|none|none| +|»»»» technicalOwnerEmail|string|false|none|none| +|»»» apiImageMetadata|[ApiImageMetadataResponse](schemas.md#schemaapiimagemetadataresponse)|false|none|none| +|»»»» **additionalProperties**|string|false|none|none| +|»»» tags|[string]|false|none|none| +|»»» labels|[string]|false|none|none| + +*and* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»» *anonymous*|object|false|none|none| +|»»» id|string|false|none|The API's handle (unique per org). Not the internal database uuid.| +|»»» refId|string¦null|false|none|Platform API (Control Plane) reference ID for this API. Used for MCP registry visibility filtering and included in outbound webhook event payloads. Null/absent for APIs that exist only in the API Portal and are not registered with the Platform API — e.g. MCP servers published via the registry.| +|»»» dataSource|string¦null|false|none|Indicates which content matched the search term: `METADATA` if the match was in the API's own metadata, or a content type (e.g. a value from the API Content `type` field) if the match was inside an uploaded content file. Only computed by getAllApiMetadataForOrganization when both the `query` search parameter is supplied and the database is PostgreSQL — absent on SQLite (the dev default) and absent from every other operation (get/create/update single API).| +|»»» planId|string|false|none|none| +|»»» endPoints|[ApiEndpointsResponse](schemas.md#schemaapiendpointsresponse)|false|none|none| +|»»»» sandboxURL|string|false|none|none| +|»»»» productionURL|string|false|none|none| +|»»» subscriptionPlans|[[SubscriptionPlanResponse](schemas.md#schemasubscriptionplanresponse)]|false|none|none| +|»»»» id|string|false|none|The plan's handle (unique per org). Not the internal database uuid.| +|»»»» displayName|string|false|none|none| +|»»»» description|string|false|none|none| +|»»»» limits|[object]|false|none|Rate/quota limits enforced for this plan. Empty when the plan is unlimited.| +|»»»»» limitType|string|false|none|none| +|»»»»» limitCount|any|false|none|Returned as a string when the stored count exceeds the safe integer range, otherwise a number. Unlimited plans have no limit entries — the `limits` array is empty.| + +*oneOf* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»»»»»» *anonymous*|integer|false|none|none| + +*xor* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»»»»»» *anonymous*|string|false|none|none| + +*continued* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»»»»» timeUnit|string¦null|false|none|none| +|»»»»» timeAmount|integer|false|none|none| +|»»»» refId|string¦null|false|none|Platform API subscription plan UUID associated with this plan.| +|»»»» orgId|string|false|none|none| +|»»»» createdBy|string|false|none|Identity of the user who created this subscription plan, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|»»»» updatedBy|string|false|none|Identity of the user who last updated this subscription plan, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|»»»» createdAt|string(date-time)|false|none|none| +|»»»» updatedAt|string(date-time)|false|none|none| +|»»» createdBy|string|false|none|Identity of the user who created this API, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|»»» updatedBy|string|false|none|Identity of the user who last updated this API, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|»»» createdAt|string(date-time)|false|none|none| +|»»» updatedAt|string(date-time)|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|PUBLISHED| +|status|DEPRECATED| +|type|RestApi| +|type|SOAP| +|type|Mcp| +|type|WS| +|type|WebSubApi| +|type|GRAPHQL| +|agentVisibility|VISIBLE| +|agentVisibility|HIDDEN| +|limitType|REQUEST_COUNT| +|limitType|EVENT_COUNT| +|limitType|BANDWIDTH| +|limitType|TOTAL_TOKEN_COUNT| +|timeUnit|MINUTE| +|timeUnit|HOUR| +|timeUnit|DAY| +|timeUnit|MONTH| +|timeUnit|null| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Get MCP server metadata + +<a id="opIdgetMcpServer"></a> + +`GET /mcp-servers/{mcpServerId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a single MCP server metadata record by API Portal MCP server ID. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server:read`, `dp:mcp_server:manage` + +</aside> + +<h3 id="get-mcp-server-metadata-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +> Example responses +> +> 200 Response + +```json +{ + "id": "weather-api-v1", + "refId": "cp-api-12345", + "name": "Weather API", + "title": "Weather Forecast API", + "remotes": [], + "version": "v1", + "status": "PUBLISHED", + "description": "Weather forecast API.", + "type": "MCP", + "agentVisibility": "VISIBLE", + "labels": [ + "default" + ], + "endPoints": { + "sandboxURL": "https://sandbox.example.com/weather", + "productionURL": "https://api.example.com/weather" + }, + "subscriptionPlans": [ + { + "id": "Gold" + } + ] +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +``` +"string" +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-mcp-server-metadata-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|MCP server metadata DTO returned by the service. Always typed `MCP`.|[ApiMetadataResponse](schemas.md#schemaapimetadataresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Plain text success response.|string| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="get-mcp-server-metadata-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Update MCP server metadata + +<a id="opIdupdateMcpServer"></a> + +`PUT /mcp-servers/{mcpServerId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' \ + -F 'artifact=@artifact.zip' \ + -F 'definition=@definition.yaml' \ + -F 'metadata={"name":"Travel Assistant MCP","version":"v1","description":"MCP server for travel planning tools","type":"MCP","agentVisibility":"VISIBLE","status":"PUBLISHED", "tags":["mcp"],"labels":["default"],"endPoints":{"productionURL":"https://mcp.example.com", "sandboxURL":"https://mcp.example.com"},"subscriptionPlans":[{"id":"Gold"}]}' + +``` + +Updates API Portal MCP server metadata and, when a `definition` is supplied, its stored tools schema. `type` is required and immutable — it must stay `MCP`; any other value is rejected with `400` via the same resolveTypeOrReject check `POST /mcp-servers` uses. An MCP server's `definition` is its tools schema, not an OpenAPI-style API contract. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server:update`, `dp:mcp_server:manage` + +</aside> + +<h3 id="update-mcp-server-metadata-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|MCP server upload. Provide the server metadata via `metadata` (a JSON string or an uploaded YAML/JSON file), or a full `artifact` ZIP, together with its `definition` — the tools, resources, and prompts the server exposes. An MCP server has no OpenAPI-style API contract; its `definition` (tools schema) IS its contract, so it is required on create (and replaces the stored schema when supplied on update).| +|» artifact|body|string(binary)|false|Full MCP server ZIP artifact containing the metadata and the definition.| +|» definition|body|string(binary)|false|MCP tools schema (YAML or JSON) listing the tools, resources, and prompts the server exposes. Required on create; when supplied on update it replaces the stored schema.| +|» metadata|body|string|false|MCP server metadata, supplied either as a JSON string field or as an uploaded YAML/JSON file (a Kubernetes-style document of kind `MCP`; file names `metadata.yaml`/`.yml`/`.json`, or `api.yaml`/`mcp.yaml`). As a JSON string it accepts these top-level fields: `name`, `version`, `description`, `type` (must be `MCP`), `agentVisibility`, `status`, `referenceId`, `id`, `tags`, `labels`, `owners`, `endPoints` (productionURL, sandboxURL), and `subscriptionPlans` (array of `{ id }` objects — only `id` is read; the plan must already exist in the organization). `id` becomes the MCP server's stored handle.| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +> Example responses +> +> 200 Response + +```json +{ + "id": "weather-api-v1", + "refId": "cp-api-12345", + "name": "Weather API", + "title": "Weather Forecast API", + "remotes": [], + "version": "v1", + "status": "PUBLISHED", + "description": "Weather forecast API.", + "type": "MCP", + "agentVisibility": "VISIBLE", + "labels": [ + "default" + ], + "endPoints": { + "sandboxURL": "https://sandbox.example.com/weather", + "productionURL": "https://api.example.com/weather" + }, + "subscriptionPlans": [ + { + "id": "Gold" + } + ] +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="update-mcp-server-metadata-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|MCP server metadata DTO returned by the service. Always typed `MCP`.|[ApiMetadataResponse](schemas.md#schemaapimetadataresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="update-mcp-server-metadata-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Delete MCP server metadata + +<a id="opIddeleteMcpServer"></a> + +`DELETE /mcp-servers/{mcpServerId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/mcp-servers/{mcpServerId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: text/plain' + +``` + +Deletes MCP server metadata when the MCP server has no active subscriptions. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:mcp_server:delete`, `dp:mcp_server:manage` + +</aside> + +<h3 id="delete-mcp-server-metadata-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|mcpServerId|path|string|true|The MCP server's handle (unique per org).| + +> Example responses +> +> 200 Response + +``` +"string" +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="delete-mcp-server-metadata-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Plain text success response.|string| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="delete-mcp-server-metadata-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| diff --git a/en/docs/api-portal/next/rest-api/organization-content.md b/en/docs/api-portal/next/rest-api/organization-content.md new file mode 100644 index 000000000..634da5159 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/organization-content.md @@ -0,0 +1,310 @@ +--- +title: "Organization Content" +description: "Get, apply, reset, and download an organization's theme assets via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/organization-content/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/organization-content.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Organization Content + +## Get a theme asset + +<a id="opIdgetOrgAsset"></a> + +`GET /views/{viewId}/asset` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/views/{viewId}/asset?fileType=string&fileName=string \ + -u {username}:{password} \ + -H 'Accept: text/css' + +``` + +Retrieves a single organization theme asset (CSS, image, etc.) by `fileType` and `fileName` query parameters. The response content type is derived from the stored file type and extension. + +<h3 id="get-a-theme-asset-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|fileType|query|string|true|Organization content file type, such as style, image, text, template, or partial.| +|fileName|query|string|true|Stored organization content file name.| +|filePath|query|string|false|Optional relative content path used together with `fileType` and `fileName`.| +|orgId|query|string|false|Deprecated and ignored. Accepted only so existing callers (the portal's own style-URL rewrite appends it) are not rejected. The organization is always this instance's own — from the session when there is one, otherwise from `organization.handle` configuration. It was previously honoured on this unauthenticated endpoint, which made it a selector for any organization's branding in a shared database.| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| + +> Example responses +> +> 200 Response + +``` +"string" +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-a-theme-asset-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Stored organization content asset.|string| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="get-a-theme-asset-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Apply a theme + +<a id="opIdapplyTheme"></a> + +`POST /views/{viewId}/apply-theme` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/views/{viewId}/apply-theme \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' \ + -F 'file=@theme.zip' + +``` + +Uploads a ZIP file and atomically replaces the view's theme assets. Only the assets contained in the uploaded ZIP are present afterward. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:organization_content:manage`, `dp:organization:manage` + +</aside> + +<h3 id="apply-a-theme-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|object|true|ZIP file upload. Organization content uploads are limited to 50 MB.| +|» file|body|string(binary)|true|ZIP file containing organization theme assets.| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "string", + "fileName": "string" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="apply-a-theme-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Organization content upload accepted and stored successfully.|[OrganizationContentUploadResponse](schemas.md#schemaorganizationcontentuploadresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="apply-a-theme-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Reset theme to defaults + +<a id="opIdresetTheme"></a> + +`POST /views/{viewId}/reset-theme` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/views/{viewId}/reset-theme \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes all stored theme assets for the view, reverting it to built-in defaults. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:organization_content:manage`, `dp:organization:manage` + +</aside> + +<h3 id="reset-theme-to-defaults-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| + +> Example responses +> +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="reset-theme-to-defaults-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Theme reset successfully.|None| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Download the current theme + +<a id="opIdexportTheme"></a> + +`GET /views/{viewId}/export-theme` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/views/{viewId}/export-theme \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/zip' + +``` + +Bundles the view's current custom theme assets into a single ZIP archive for download. The archive is wrapped in a top-level folder so it can be re-uploaded via the apply-theme endpoint. Returns 404 when the view has no custom theme. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:organization_content:read`, `dp:organization:manage` + +</aside> + +<h3 id="download-the-current-theme-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| + +> Example responses +> +> 200 Response + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="download-the-current-theme-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Theme archive.|string| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| diff --git a/en/docs/api-portal/next/rest-api/organizations.md b/en/docs/api-portal/next/rest-api/organizations.md new file mode 100644 index 000000000..e6772163f --- /dev/null +++ b/en/docs/api-portal/next/rest-api/organizations.md @@ -0,0 +1,522 @@ +--- +title: "Organizations" +description: "Create, list, update, and delete organizations via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/organizations/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/organizations.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Organizations + +## Create an organization (not supported) + +<a id="opIdcreateOrganization"></a> + +`POST /organizations` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/organizations \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +NOT SUPPORTED. A well-formed request returns 405; a malformed one can still fail validation first with 400, or 415 for an unsupported content type. This API Portal serves the single organization named by its `organization.handle` configuration, which is created on startup along with its default portal configuration, label, view, and subscription plans. The operation is retained for forward compatibility. + +> Payload + +```json +{ + "displayName": "Acme Corporation", + "businessOwner": "string", + "businessOwnerContact": "string", + "businessOwnerEmail": "user@example.com", + "id": "acme", + "idpRefId": "string", + "cpRefId": "string", + "configuration": {} +} +``` + +```yaml +displayName: Acme Corporation +businessOwner: string +businessOwnerContact: string +businessOwnerEmail: user@example.com +id: acme +idpRefId: string +cpRefId: string +configuration: {} + +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:organization:create`, `dp:organization:manage` + +</aside> + +<h3 id="create-an-organization-(not-supported)-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[OrganizationCreateRequest](schemas.md#schemaorganizationcreaterequest)|true|Organization creation payload. Send JSON or an organization YAML file in the `organization` multipart field. The JSON example below applies only to the `application/json` content type. When an organization YAML **file** is uploaded instead, its content must use `kind: Organization` with the nested shape `metadata.name` (handle, any top-level `id` is ignored) and `spec.displayName`; all other fields (including `cpRefId`) are read from `spec`. The YAML `spec` block additionally accepts `labels` (array of `{name, displayName}`) and `views` (array of `{id, displayName, labels}` — `id` becomes the view's handle) to bootstrap labels and views at creation time — these are not available via the `application/json` content type.| + +> Example responses +> +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 405 Response + +```json +{ + "status": "error", + "code": "METHOD_NOT_ALLOWED", + "message": "This API Portal serves a single organization, which is configured and provisioned at startup. Organizations cannot be created, listed, or deleted through the API." +} +``` + +> 415 Response + +```json +{ + "status": "error", + "code": "UNSUPPORTED_MEDIA_TYPE", + "message": "Content-Type must be application/json." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="create-an-organization-(not-supported)-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|405|[Method Not Allowed](https://tools.ietf.org/html/rfc7231#section-6.5.5)|The operation is not offered by this deployment. Returned by the organization lifecycle operations, which an API Portal serving a single organization does not expose — that organization is configured and provisioned at startup.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|415|[Unsupported Media Type](https://tools.ietf.org/html/rfc7231#section-6.5.13)|Unsupported request media type.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="create-an-organization-(not-supported)-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## List organizations (not supported) + +<a id="opIdgetOrganizations"></a> + +`GET /organizations` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/organizations \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +NOT SUPPORTED. A well-formed request returns 405; a malformed one can still fail validation first with 400. Listing is inherently cross-organization, and this API Portal serves exactly one. Use `GET /organizations/{orgId}` with this instance's own handle instead. The operation is retained for forward compatibility. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:organization:read`, `dp:organization:manage` + +</aside> + +> Example responses +> +> 405 Response + +```json +{ + "status": "error", + "code": "METHOD_NOT_ALLOWED", + "message": "This API Portal serves a single organization, which is configured and provisioned at startup. Organizations cannot be created, listed, or deleted through the API." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-organizations-(not-supported)-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|405|[Method Not Allowed](https://tools.ietf.org/html/rfc7231#section-6.5.5)|The operation is not offered by this deployment. Returned by the organization lifecycle operations, which an API Portal serving a single organization does not expose — that organization is configured and provisioned at startup.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Update an organization + +<a id="opIdupdateOrganization"></a> + +`PUT /organizations/{orgId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/organizations/{orgId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates organization metadata, claim mappings, role mappings, and portal configuration. `orgId` must name this instance's own organization; any other returns 403. The `id` (handle) and `idpRefId` fields cannot be changed — they are what page URLs and incoming token organization claims are matched against, so a rename would leave the running instance unable to find its own organization. Sending a different value returns 400. + +> Payload + +```json +{ + "displayName": "Acme Corporation", + "businessOwner": "string", + "businessOwnerContact": "string", + "businessOwnerEmail": "user@example.com", + "id": "acme", + "idpRefId": "string", + "cpRefId": "string", + "configuration": {} +} +``` + +```yaml +displayName: Acme Corporation +businessOwner: string +businessOwnerContact: string +businessOwnerEmail: user@example.com +id: acme +idpRefId: string +cpRefId: string +configuration: {} + +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:organization:update`, `dp:organization:manage` + +</aside> + +<h3 id="update-an-organization-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[OrganizationUpdateRequest](schemas.md#schemaorganizationupdaterequest)|true|Organization update payload. Send JSON or an organization YAML file in the `organization` multipart field. The JSON example below applies only to the `application/json` content type. When an organization YAML **file** is uploaded instead, its content must use `kind: Organization` with the nested shape `metadata.name` (handle, any top-level `id` is ignored) and `spec.displayName`; all other fields (including `cpRefId`) are read from `spec`. The YAML `spec` block additionally accepts `labels` (upserted by name) and `views` (upserted by `id`, which becomes the view's handle, with `labels` replacing the view's label set) — these are not available via the `application/json` content type.| +|orgId|path|string|true|The organization's handle (also matches by name or IDP reference ID). Not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "acme", + "displayName": "Acme Corporation", + "businessOwner": "string", + "businessOwnerContact": "string", + "businessOwnerEmail": "user@example.com", + "idpRefId": "string", + "cpRefId": "string", + "configuration": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="update-an-organization-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Organization DTO returned by create, update, and lookup operations.|[OrganizationResponse](schemas.md#schemaorganizationresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="update-an-organization-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Get an organization + +<a id="opIdgetOrganization"></a> + +`GET /organizations/{orgId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/organizations/{orgId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves this instance's organization by organization name, handle, or identity provider (IDP) reference ID. Because the portal serves a single organization, `orgId` must resolve to that one; any other organization returns 403 — and so does an organization that does not exist, so the response cannot be used to discover which organizations the shared database holds. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:organization:read`, `dp:organization:manage` + +</aside> + +<h3 id="get-an-organization-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|orgId|path|string|true|The organization's handle (also matches by name or IDP reference ID). Not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "acme", + "displayName": "Acme Corporation", + "businessOwner": "string", + "businessOwnerContact": "string", + "businessOwnerEmail": "user@example.com", + "idpRefId": "string", + "cpRefId": "string", + "configuration": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-an-organization-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Organization DTO returned by create, update, and lookup operations.|[OrganizationResponse](schemas.md#schemaorganizationresponse)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Delete an organization (not supported) + +<a id="opIddeleteOrganization"></a> + +`DELETE /organizations/{orgId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/organizations/{orgId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +NOT SUPPORTED. A well-formed request returns 405; a malformed one can still fail validation first with 400. This API Portal instance is bound to a single organization for its whole lifetime; deleting it would leave the instance serving nothing. The operation is retained for forward compatibility. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:organization:delete`, `dp:organization:manage` + +</aside> + +<h3 id="delete-an-organization-(not-supported)-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|orgId|path|string|true|The organization's handle (also matches by name or IDP reference ID). Not the internal database uuid.| + +> Example responses +> +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 405 Response + +```json +{ + "status": "error", + "code": "METHOD_NOT_ALLOWED", + "message": "This API Portal serves a single organization, which is configured and provisioned at startup. Organizations cannot be created, listed, or deleted through the API." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="delete-an-organization-(not-supported)-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|405|[Method Not Allowed](https://tools.ietf.org/html/rfc7231#section-6.5.5)|The operation is not offered by this deployment. Returned by the organization lifecycle operations, which an API Portal serving a single organization does not expose — that organization is configured and provisioned at startup.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="delete-an-organization-(not-supported)-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| diff --git a/en/docs/api-portal/next/rest-api/overview.md b/en/docs/api-portal/next/rest-api/overview.md new file mode 100644 index 000000000..9f8656674 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/overview.md @@ -0,0 +1,177 @@ +--- +title: "API Portal Management API" +description: "Overview of the API Portal Management API for managing organizations, APIs, MCP servers, applications, subscriptions, and API keys." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/overview.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# API Portal Management API + +Fine-grained API Portal REST API for managing organizations, +API metadata and content, applications, subscriptions, application appKeyMappings, and API flows. + +All resources, including organization lifecycle endpoints +(`/api/v0.9/organizations`, `/api/v0.9/organizations/{orgId}`), are served under `/api/v0.9`. +Operations declare the least-privilege OAuth2 scopes required for each resource action. + +Base URLs: +* <a href="https://localhost:9543/api/v0.9">https://localhost:9543/api/v0.9</a> +* <a href="http://localhost:9543/api/v0.9">http://localhost:9543/api/v0.9</a> + +## Table of Contents + +### [Authentication](authentication.md) + +### [Organizations](organizations.md) + +- [Create an organization (not supported)](organizations.md#create-an-organization-not-supported) +- [List organizations (not supported)](organizations.md#list-organizations-not-supported) +- [Update an organization](organizations.md#update-an-organization) +- [Get an organization](organizations.md#get-an-organization) +- [Delete an organization (not supported)](organizations.md#delete-an-organization-not-supported) + +### [Organization Content](organization-content.md) + +- [Get a theme asset](organization-content.md#get-a-theme-asset) +- [Apply a theme](organization-content.md#apply-a-theme) +- [Reset theme to defaults](organization-content.md#reset-theme-to-defaults) +- [Download the current theme](organization-content.md#download-the-current-theme) + +### [APIs](apis.md) + +- [Create API metadata](apis.md#create-api-metadata) +- [List API metadata](apis.md#list-api-metadata) +- [Get API metadata](apis.md#get-api-metadata) +- [Update API metadata](apis.md#update-api-metadata) +- [Delete API metadata](apis.md#delete-api-metadata) + +### [API Content](api-content.md) + +- [Upload API content](api-content.md#upload-api-content) +- [Replace API content](api-content.md#replace-api-content) +- [Get an API content file](api-content.md#get-an-api-content-file) +- [Delete API content files](api-content.md#delete-api-content-files) + +### [MCP Servers](mcp-servers.md) + +- [Create MCP server metadata](mcp-servers.md#create-mcp-server-metadata) +- [List MCP server metadata](mcp-servers.md#list-mcp-server-metadata) +- [Get MCP server metadata](mcp-servers.md#get-mcp-server-metadata) +- [Update MCP server metadata](mcp-servers.md#update-mcp-server-metadata) +- [Delete MCP server metadata](mcp-servers.md#delete-mcp-server-metadata) + +### [MCP Server Content](mcp-server-content.md) + +- [Upload MCP server content](mcp-server-content.md#upload-mcp-server-content) +- [Replace MCP server content](mcp-server-content.md#replace-mcp-server-content) +- [Get an MCP server content file](mcp-server-content.md#get-an-mcp-server-content-file) +- [Delete MCP server content files](mcp-server-content.md#delete-mcp-server-content-files) + +### [MCP Server Keys](mcp-server-keys.md) + +- [Generate an MCP server API key](mcp-server-keys.md#generate-an-mcp-server-api-key) +- [List MCP server API keys](mcp-server-keys.md#list-mcp-server-api-keys) +- [Regenerate an MCP server API key](mcp-server-keys.md#regenerate-an-mcp-server-api-key) +- [Revoke an MCP server API key](mcp-server-keys.md#revoke-an-mcp-server-api-key) +- [Associate an MCP server API key with an application](mcp-server-keys.md#associate-an-mcp-server-api-key-with-an-application) +- [Remove an MCP server API key's application association](mcp-server-keys.md#remove-an-mcp-server-api-keys-application-association) + +### [Subscription Plans](subscription-plans.md) + +- [List subscription plans](subscription-plans.md#list-subscription-plans) +- [Create subscription plans](subscription-plans.md#create-subscription-plans) +- [Upsert subscription plans](subscription-plans.md#upsert-subscription-plans) +- [Get a subscription plan](subscription-plans.md#get-a-subscription-plan) +- [Delete a subscription plan](subscription-plans.md#delete-a-subscription-plan) + +### [Labels](labels.md) + +- [Create a label](labels.md#create-a-label) +- [List labels](labels.md#list-labels) +- [Get a label](labels.md#get-a-label) +- [Update a label](labels.md#update-a-label) +- [Delete a label](labels.md#delete-a-label) + +### [Applications](applications.md) + +- [List applications for the authenticated user](applications.md#list-applications-for-the-authenticated-user) +- [Create an application](applications.md#create-an-application) +- [Get an application](applications.md#get-an-application) +- [Update an application](applications.md#update-an-application) +- [Delete an application](applications.md#delete-an-application) + +### [Subscriptions](subscriptions.md) + +- [Create a subscription](subscriptions.md#create-a-subscription) +- [List subscriptions](subscriptions.md#list-subscriptions) +- [Get a subscription](subscriptions.md#get-a-subscription) +- [Update a subscription](subscriptions.md#update-a-subscription) +- [Delete a subscription](subscriptions.md#delete-a-subscription) +- [Change subscription plan](subscriptions.md#change-subscription-plan) +- [Regenerate subscription token](subscriptions.md#regenerate-subscription-token) + +### [API Keys](api-keys.md) + +- [List all API keys for the current user](api-keys.md#list-all-api-keys-for-the-current-user) +- [Generate an API key](api-keys.md#generate-an-api-key) +- [List API keys](api-keys.md#list-api-keys) +- [Regenerate an API key](api-keys.md#regenerate-an-api-key) +- [Revoke an API key](api-keys.md#revoke-an-api-key) +- [Associate an API key with an application](api-keys.md#associate-an-api-key-with-an-application) +- [Remove an API key's application association](api-keys.md#remove-an-api-keys-application-association) +- [List API keys associated with an application](api-keys.md#list-api-keys-associated-with-an-application) + +### [Views](views.md) + +- [Create a view](views.md#create-a-view) +- [List views](views.md#list-views) +- [Update a view](views.md#update-a-view) +- [Get a view](views.md#get-a-view) +- [Delete a view](views.md#delete-a-view) + +### [Application Keys](application-keys.md) + +- [Map an OAuth `client_id` to an API Portal application](application-keys.md#map-an-oauth-client_id-to-an-api-portal-application) +- [Generate an OAuth access token](application-keys.md#generate-an-oauth-access-token) +- [Remove an OAuth `client_id` mapping](application-keys.md#remove-an-oauth-client_id-mapping) + +### [API Workflows](api-workflows.md) + +- [Create an API workflow](api-workflows.md#create-an-api-workflow) +- [List API workflows](api-workflows.md#list-api-workflows) +- [Get an API workflow](api-workflows.md#get-an-api-workflow) +- [Update an API workflow](api-workflows.md#update-an-api-workflow) +- [Delete an API workflow](api-workflows.md#delete-an-api-workflow) +- [Generate an API workflow agent prompt](api-workflows.md#generate-an-api-workflow-agent-prompt) + +### [Key Managers](key-managers.md) + +- [Create a key manager](key-managers.md#create-a-key-manager) +- [List key managers](key-managers.md#list-key-managers) +- [Get a key manager](key-managers.md#get-a-key-manager) +- [Update a key manager](key-managers.md#update-a-key-manager) +- [Delete a key manager](key-managers.md#delete-a-key-manager) + +### [Webhook Events](webhook-events.md) + +- [List webhook events](webhook-events.md#list-webhook-events) +- [Get a webhook event](webhook-events.md#get-a-webhook-event) + +### [Webhook Subscribers](webhook-subscribers.md) + +- [Create a webhook subscriber](webhook-subscribers.md#create-a-webhook-subscriber) +- [List webhook subscribers](webhook-subscribers.md#list-webhook-subscribers) +- [Get a webhook subscriber](webhook-subscribers.md#get-a-webhook-subscriber) +- [Update a webhook subscriber](webhook-subscribers.md#update-a-webhook-subscriber) +- [Delete a webhook subscriber](webhook-subscribers.md#delete-a-webhook-subscriber) +- [List recent deliveries for a webhook subscriber](webhook-subscribers.md#list-recent-deliveries-for-a-webhook-subscriber) + +### [Schemas](schemas.md) + diff --git a/en/docs/api-portal/next/rest-api/schemas.md b/en/docs/api-portal/next/rest-api/schemas.md new file mode 100644 index 000000000..3d714f0ff --- /dev/null +++ b/en/docs/api-portal/next/rest-api/schemas.md @@ -0,0 +1,2045 @@ +--- +title: "Schemas" +description: "Data model schemas referenced by the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/schemas/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/schemas.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Schemas + +<h2 id="tocS_Pagination">Pagination</h2> + +<a id="schemapagination"></a> +<a id="schema_Pagination"></a> +<a id="tocSpagination"></a> +<a id="tocspagination"></a> + +```json +{ + "total": 42, + "limit": 20, + "offset": 0 +} + +``` + +Standard pagination metadata returned with collection responses. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|total|integer|true|none|Total number of records matching the query.| +|limit|integer|true|none|Maximum number of records returned in this response.| +|offset|integer|true|none|Number of records skipped before this page.| + +<h2 id="tocS_MessageResponse">MessageResponse</h2> + +<a id="schemamessageresponse"></a> +<a id="schema_MessageResponse"></a> +<a id="tocSmessageresponse"></a> +<a id="tocsmessageresponse"></a> + +```json +{ + "message": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|message|string|true|none|none| + +<h2 id="tocS_GenericValue">GenericValue</h2> + +<a id="schemagenericvalue"></a> +<a id="schema_GenericValue"></a> +<a id="tocSgenericvalue"></a> +<a id="tocsgenericvalue"></a> + +```json +{} + +``` + +### Properties + +oneOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|[any]|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|string|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|number|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|boolean|false|none|none| + +<h2 id="tocS_GenericObject">GenericObject</h2> + +<a id="schemagenericobject"></a> +<a id="schema_GenericObject"></a> +<a id="tocSgenericobject"></a> +<a id="tocsgenericobject"></a> + +```json +{} + +``` + +### Properties + +*None* + +<h2 id="tocS_ErrorResponse">ErrorResponse</h2> + +<a id="schemaerrorresponse"></a> +<a id="schema_ErrorResponse"></a> +<a id="tocSerrorresponse"></a> +<a id="tocserrorresponse"></a> + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "string", + "errors": [ + { + "field": "string", + "message": "string" + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|status|string|true|none|Always "error" for error responses.| +|code|string|true|none|Machine-readable SCREAMING_SNAKE_CASE catalog code.| +|message|string|true|none|Human-readable error message.| +|errors|[object]|false|none|Optional per-field validation errors.| +|» field|string|true|none|none| +|» message|string|true|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +<h2 id="tocS_OrganizationResponse">OrganizationResponse</h2> + +<a id="schemaorganizationresponse"></a> +<a id="schema_OrganizationResponse"></a> +<a id="tocSorganizationresponse"></a> +<a id="tocsorganizationresponse"></a> + +```json +{ + "id": "acme", + "displayName": "Acme Corporation", + "businessOwner": "string", + "businessOwnerContact": "string", + "businessOwnerEmail": "user@example.com", + "idpRefId": "string", + "cpRefId": "string", + "configuration": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|The organization's handle (unique). Not the internal database uuid.| +|displayName|string|false|none|none| +|businessOwner|string¦null|false|none|none| +|businessOwnerContact|string¦null|false|none|none| +|businessOwnerEmail|string(email)¦null|false|none|none| +|idpRefId|string|false|none|The organization claim value asserted by the configured identity provider (IDP) at single sign-on (SSO) login. On every login, the portal matches the authenticated user's org claim against this value to resolve which organization they belong to — it must exactly match the IDP's claim, or login fails for that org's users. Distinct from `cpRefId`, which is unrelated to authentication.| +|cpRefId|string¦null|false|none|Control Plane reference ID. Included in outbound webhook event payloads so subscribers can correlate this organization with its Control Plane (Platform API) counterpart. Not used for authentication or org resolution.| +|configuration|object|false|none|Free-form organization configuration set by the caller. Which artifact types the portal serves is operator configuration (`api_portal.artifacts`), not part of this.| +|createdAt|string(date-time)¦null|false|none|none| +|updatedAt|string(date-time)¦null|false|none|none| + +<h2 id="tocS_OrganizationContentUploadResponse">OrganizationContentUploadResponse</h2> + +<a id="schemaorganizationcontentuploadresponse"></a> +<a id="schema_OrganizationContentUploadResponse"></a> +<a id="tocSorganizationcontentuploadresponse"></a> +<a id="tocsorganizationcontentuploadresponse"></a> + +```json +{ + "id": "string", + "fileName": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|none|none| +|fileName|string|true|none|Original ZIP file name uploaded in the `file` multipart field.| + +<h2 id="tocS_OrganizationContentListItemResponse">OrganizationContentListItemResponse</h2> + +<a id="schemaorganizationcontentlistitemresponse"></a> +<a id="schema_OrganizationContentListItemResponse"></a> +<a id="tocSorganizationcontentlistitemresponse"></a> +<a id="tocsorganizationcontentlistitemresponse"></a> + +```json +{ + "id": "string", + "fileName": "string", + "fileContent": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|none| +|fileName|string|false|none|none| +|fileContent|string¦null|false|none|UTF-8 content string returned for stored organization content records.| + +<h2 id="tocS_ApiMetadataCreateResponse">ApiMetadataCreateResponse</h2> + +<a id="schemaapimetadatacreateresponse"></a> +<a id="schema_ApiMetadataCreateResponse"></a> +<a id="tocSapimetadatacreateresponse"></a> +<a id="tocsapimetadatacreateresponse"></a> + +```json +{ + "name": "string", + "title": "string", + "remotes": [ + {} + ], + "version": "string", + "status": "PUBLISHED", + "description": "string", + "type": "RestApi", + "referenceId": "string", + "agentVisibility": "VISIBLE", + "addedLabels": [ + "string" + ], + "removedLabels": [ + "string" + ], + "owners": { + "technicalOwner": "string", + "businessOwner": "string", + "businessOwnerEmail": "string", + "technicalOwnerEmail": "string" + }, + "apiImageMetadata": { + "property1": "string", + "property2": "string" + }, + "tags": [ + "string" + ], + "labels": [ + "string" + ], + "id": "string", + "refId": "string", + "endPoints": { + "sandboxURL": "string", + "productionURL": "string" + }, + "subscriptionPlans": [ + { + "id": "string", + "displayName": "string", + "description": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "limitCount": 10000, + "timeUnit": "MINUTE", + "timeAmount": 1 + } + ], + "refId": "string", + "orgId": "string", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" + } + ] +} + +``` + +### Properties + +allOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|[ApiInfoResponse](#schemaapiinforesponse)|false|none|Fields are returned at the root of ApiMetadataResponse / ApiMetadataCreateResponse (not nested under an `apiInfo` key) — this schema exists only to share the field set between the two via `allOf`.| + +and + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| +|» id|string|false|none|The API's handle (unique per org). Not the internal database uuid.| +|» refId|string¦null|false|none|Platform API (Control Plane) reference ID for this API. Used for MCP registry visibility filtering and included in outbound webhook event payloads. Null/absent for APIs that exist only in the API Portal and are not registered with the Platform API — e.g. MCP servers published via the registry.| +|» endPoints|[ApiEndpointsResponse](#schemaapiendpointsresponse)|false|none|none| +|» subscriptionPlans|[[SubscriptionPlanResponse](#schemasubscriptionplanresponse)]|false|none|none| + +<h2 id="tocS_ApiMetadataResponse">ApiMetadataResponse</h2> + +<a id="schemaapimetadataresponse"></a> +<a id="schema_ApiMetadataResponse"></a> +<a id="tocSapimetadataresponse"></a> +<a id="tocsapimetadataresponse"></a> + +```json +{ + "name": "string", + "title": "string", + "remotes": [ + {} + ], + "version": "string", + "status": "PUBLISHED", + "description": "string", + "type": "RestApi", + "referenceId": "string", + "agentVisibility": "VISIBLE", + "addedLabels": [ + "string" + ], + "removedLabels": [ + "string" + ], + "owners": { + "technicalOwner": "string", + "businessOwner": "string", + "businessOwnerEmail": "string", + "technicalOwnerEmail": "string" + }, + "apiImageMetadata": { + "property1": "string", + "property2": "string" + }, + "tags": [ + "string" + ], + "labels": [ + "string" + ], + "id": "string", + "refId": "string", + "dataSource": "string", + "planId": "string", + "endPoints": { + "sandboxURL": "string", + "productionURL": "string" + }, + "subscriptionPlans": [ + { + "id": "string", + "displayName": "string", + "description": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "limitCount": 10000, + "timeUnit": "MINUTE", + "timeAmount": 1 + } + ], + "refId": "string", + "orgId": "string", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" +} + +``` + +### Properties + +allOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|[ApiInfoResponse](#schemaapiinforesponse)|false|none|Fields are returned at the root of ApiMetadataResponse / ApiMetadataCreateResponse (not nested under an `apiInfo` key) — this schema exists only to share the field set between the two via `allOf`.| + +and + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| +|» id|string|false|none|The API's handle (unique per org). Not the internal database uuid.| +|» refId|string¦null|false|none|Platform API (Control Plane) reference ID for this API. Used for MCP registry visibility filtering and included in outbound webhook event payloads. Null/absent for APIs that exist only in the API Portal and are not registered with the Platform API — e.g. MCP servers published via the registry.| +|» dataSource|string¦null|false|none|Indicates which content matched the search term: `METADATA` if the match was in the API's own metadata, or a content type (e.g. a value from the API Content `type` field) if the match was inside an uploaded content file. Only computed by getAllApiMetadataForOrganization when both the `query` search parameter is supplied and the database is PostgreSQL — absent on SQLite (the dev default) and absent from every other operation (get/create/update single API).| +|» planId|string|false|none|none| +|» endPoints|[ApiEndpointsResponse](#schemaapiendpointsresponse)|false|none|none| +|» subscriptionPlans|[[SubscriptionPlanResponse](#schemasubscriptionplanresponse)]|false|none|none| +|» createdBy|string|false|none|Identity of the user who created this API, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|» updatedBy|string|false|none|Identity of the user who last updated this API, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|» createdAt|string(date-time)|false|none|none| +|» updatedAt|string(date-time)|false|none|none| + +<h2 id="tocS_ApiInfoResponse">ApiInfoResponse</h2> + +<a id="schemaapiinforesponse"></a> +<a id="schema_ApiInfoResponse"></a> +<a id="tocSapiinforesponse"></a> +<a id="tocsapiinforesponse"></a> + +```json +{ + "name": "string", + "title": "string", + "remotes": [ + {} + ], + "version": "string", + "status": "PUBLISHED", + "description": "string", + "type": "RestApi", + "referenceId": "string", + "agentVisibility": "VISIBLE", + "addedLabels": [ + "string" + ], + "removedLabels": [ + "string" + ], + "owners": { + "technicalOwner": "string", + "businessOwner": "string", + "businessOwnerEmail": "string", + "technicalOwnerEmail": "string" + }, + "apiImageMetadata": { + "property1": "string", + "property2": "string" + }, + "tags": [ + "string" + ], + "labels": [ + "string" + ] +} + +``` + +Fields are returned at the root of ApiMetadataResponse / ApiMetadataCreateResponse (not nested under an `apiInfo` key) — this schema exists only to share the field set between the two via `allOf`. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|name|string|false|none|none| +|title|string¦null|false|none|none| +|remotes|[object]|false|none|none| +|version|string|false|none|none| +|status|string|false|none|API lifecycle status.| +|description|string|false|none|none| +|type|string|false|none|The stored/returned type constant (src/utils/constants.js API_TYPE) — distinct from the request-time keyword accepted on create/update (see `type` in ApiMetadataMultipartBody: REST, SOAP, MCP, WS, WEBSUB, GRAPHQL). REST maps to `RestApi` and WEBSUB maps to `WebSubApi`; the rest are returned unchanged.| +|referenceId|string¦null|false|none|External reference ID. Present when the API was created from a YAML artifact whose `spec` block sets `referenceId` — the create response echoes the parsed YAML back.| +|agentVisibility|string|false|none|none| +|addedLabels|[string]|false|none|none| +|removedLabels|[string]|false|none|none| +|owners|[ApiOwnersResponse](#schemaapiownersresponse)|false|none|none| +|apiImageMetadata|[ApiImageMetadataResponse](#schemaapiimagemetadataresponse)|false|none|none| +|tags|[string]|false|none|none| +|labels|[string]|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|PUBLISHED| +|status|DEPRECATED| +|type|RestApi| +|type|SOAP| +|type|Mcp| +|type|WS| +|type|WebSubApi| +|type|GRAPHQL| +|agentVisibility|VISIBLE| +|agentVisibility|HIDDEN| + +<h2 id="tocS_ApiOwnersResponse">ApiOwnersResponse</h2> + +<a id="schemaapiownersresponse"></a> +<a id="schema_ApiOwnersResponse"></a> +<a id="tocSapiownersresponse"></a> +<a id="tocsapiownersresponse"></a> + +```json +{ + "technicalOwner": "string", + "businessOwner": "string", + "businessOwnerEmail": "string", + "technicalOwnerEmail": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|technicalOwner|string|false|none|none| +|businessOwner|string|false|none|none| +|businessOwnerEmail|string|false|none|none| +|technicalOwnerEmail|string|false|none|none| + +<h2 id="tocS_ApiEndpointsResponse">ApiEndpointsResponse</h2> + +<a id="schemaapiendpointsresponse"></a> +<a id="schema_ApiEndpointsResponse"></a> +<a id="tocSapiendpointsresponse"></a> +<a id="tocsapiendpointsresponse"></a> + +```json +{ + "sandboxURL": "string", + "productionURL": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|sandboxURL|string|false|none|none| +|productionURL|string|false|none|none| + +<h2 id="tocS_ApiImageMetadataResponse">ApiImageMetadataResponse</h2> + +<a id="schemaapiimagemetadataresponse"></a> +<a id="schema_ApiImageMetadataResponse"></a> +<a id="tocSapiimagemetadataresponse"></a> +<a id="tocsapiimagemetadataresponse"></a> + +```json +{ + "property1": "string", + "property2": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|**additionalProperties**|string|false|none|none| + +<h2 id="tocS_SubscriptionPlanResponse">SubscriptionPlanResponse</h2> + +<a id="schemasubscriptionplanresponse"></a> +<a id="schema_SubscriptionPlanResponse"></a> +<a id="tocSsubscriptionplanresponse"></a> +<a id="tocssubscriptionplanresponse"></a> + +```json +{ + "id": "string", + "displayName": "string", + "description": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "limitCount": 10000, + "timeUnit": "MINUTE", + "timeAmount": 1 + } + ], + "refId": "string", + "orgId": "string", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|The plan's handle (unique per org). Not the internal database uuid.| +|displayName|string|false|none|none| +|description|string|false|none|none| +|limits|[object]|false|none|Rate/quota limits enforced for this plan. Empty when the plan is unlimited.| +|» limitType|string|false|none|none| +|» limitCount|any|false|none|Returned as a string when the stored count exceeds the safe integer range, otherwise a number. Unlimited plans have no limit entries — the `limits` array is empty.| + +oneOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»» *anonymous*|integer|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»» *anonymous*|string|false|none|none| + +continued + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» timeUnit|string¦null|false|none|none| +|» timeAmount|integer|false|none|none| +|refId|string¦null|false|none|Platform API subscription plan UUID associated with this plan.| +|orgId|string|false|none|none| +|createdBy|string|false|none|Identity of the user who created this subscription plan, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|updatedBy|string|false|none|Identity of the user who last updated this subscription plan, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|limitType|REQUEST_COUNT| +|limitType|EVENT_COUNT| +|limitType|BANDWIDTH| +|limitType|TOTAL_TOKEN_COUNT| +|timeUnit|MINUTE| +|timeUnit|HOUR| +|timeUnit|DAY| +|timeUnit|MONTH| +|timeUnit|null| + +<h2 id="tocS_LabelResponse">LabelResponse</h2> + +<a id="schemalabelresponse"></a> +<a id="schema_LabelResponse"></a> +<a id="tocSlabelresponse"></a> +<a id="tocslabelresponse"></a> + +```json +{ + "id": "premium", + "displayName": "Premium APIs" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|The label's handle (unique per org). Not the internal database uuid.| +|displayName|string|false|none|none| + +<h2 id="tocS_ApplicationResponse">ApplicationResponse</h2> + +<a id="schemaapplicationresponse"></a> +<a id="schema_ApplicationResponse"></a> +<a id="tocSapplicationresponse"></a> +<a id="tocsapplicationresponse"></a> + +```json +{ + "id": "my-weather-app", + "displayName": "Weather App", + "description": "Application used to call Weather APIs.", + "appKeyMappings": [ + { + "asClientId": "asgardeo-client-abc123", + "kmId": "km-uuid-12345", + "type": "PRODUCTION" + } + ], + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|The application's handle (unique per org). Not the internal database uuid.| +|displayName|string|false|none|none| +|description|string|false|none|none| +|appKeyMappings|[[ApplicationKeyMappingSummary](#schemaapplicationkeymappingsummary)]|false|none|[OAuth client ID mapping entry attached to an application.]| +|createdBy|string|false|none|Identity of the user who created this application, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|updatedBy|string|false|none|Identity of the user who last updated this application, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| + +<h2 id="tocS_ApplicationKeyMappingSummary">ApplicationKeyMappingSummary</h2> + +<a id="schemaapplicationkeymappingsummary"></a> +<a id="schema_ApplicationKeyMappingSummary"></a> +<a id="tocSapplicationkeymappingsummary"></a> +<a id="tocsapplicationkeymappingsummary"></a> + +```json +{ + "asClientId": "asgardeo-client-abc123", + "kmId": "km-uuid-12345", + "type": "PRODUCTION" +} + +``` + +OAuth client ID mapping entry attached to an application. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|asClientId|string|false|none|OAuth client ID, created directly in the key manager and linked to this application.| +|kmId|string|false|none|UUID of the key manager this client ID is linked to.| +|type|string|false|none|Key type for this mapping.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|type|PRODUCTION| +|type|SANDBOX| + +<h2 id="tocS_ViewResponse">ViewResponse</h2> + +<a id="schemaviewresponse"></a> +<a id="schema_ViewResponse"></a> +<a id="tocSviewresponse"></a> +<a id="tocsviewresponse"></a> + +```json +{ + "id": "partner-apis", + "displayName": "Partner APIs", + "labels": [ + "partner", + "public" + ], + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|none|The view's handle (unique per org). Not the internal database uuid.| +|displayName|string|true|none|none| +|labels|[string]|true|none|none| +|createdBy|string|false|none|Identity of the user who created this view, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|updatedBy|string|false|none|Identity of the user who last updated this view, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| + +<h2 id="tocS_OrganizationCreateRequest">OrganizationCreateRequest</h2> + +<a id="schemaorganizationcreaterequest"></a> +<a id="schema_OrganizationCreateRequest"></a> +<a id="tocSorganizationcreaterequest"></a> +<a id="tocsorganizationcreaterequest"></a> + +```json +{ + "displayName": "Acme Corporation", + "businessOwner": "string", + "businessOwnerContact": "string", + "businessOwnerEmail": "user@example.com", + "id": "acme", + "idpRefId": "string", + "cpRefId": "string", + "configuration": {} +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|displayName|string|true|none|none| +|businessOwner|string|false|none|none| +|businessOwnerContact|string|false|none|none| +|businessOwnerEmail|string(email)|false|none|none| +|id|string|true|none|Desired handle for the organization (unique), stored as-is. Used in portal URLs.| +|idpRefId|string|true|none|The organization claim value asserted by the configured Identity Provider at SSO login. Must exactly match the IDP's org claim for that org's users, or login will fail. Distinct from `cpRefId`.| +|cpRefId|string¦null|false|none|Control Plane reference ID, included in outbound webhook event payloads. Not used for authentication.| +|configuration|object|false|none|Free-form organization configuration.| + +<h2 id="tocS_OrganizationUpdateRequest">OrganizationUpdateRequest</h2> + +<a id="schemaorganizationupdaterequest"></a> +<a id="schema_OrganizationUpdateRequest"></a> +<a id="tocSorganizationupdaterequest"></a> +<a id="tocsorganizationupdaterequest"></a> + +```json +{ + "displayName": "Acme Corporation", + "businessOwner": "string", + "businessOwnerContact": "string", + "businessOwnerEmail": "user@example.com", + "id": "acme", + "idpRefId": "string", + "cpRefId": "string", + "configuration": {} +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|displayName|string|true|none|none| +|businessOwner|string|false|none|none| +|businessOwnerContact|string|false|none|none| +|businessOwnerEmail|string(email)|false|none|none| +|id|string|true|none|Desired handle for the organization (unique), stored as-is. Used in portal URLs.| +|idpRefId|string|true|none|The organization claim value asserted by the configured Identity Provider at SSO login. Must exactly match the IDP's org claim for that org's users, or login will fail. Distinct from `cpRefId`.| +|cpRefId|string¦null|false|none|Control Plane reference ID, included in outbound webhook event payloads. Not used for authentication.| +|configuration|object|false|none|Free-form organization configuration.| + +<h2 id="tocS_SubscriptionPlanRequest">SubscriptionPlanRequest</h2> + +<a id="schemasubscriptionplanrequest"></a> +<a id="schema_SubscriptionPlanRequest"></a> +<a id="tocSsubscriptionplanrequest"></a> +<a id="tocssubscriptionplanrequest"></a> + +```json +{ + "id": "Gold", + "refId": "string", + "displayName": "string", + "description": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "limitCount": 10000, + "timeUnit": "MINUTE", + "timeAmount": 1 + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Optional desired handle for the plan (unique per org), stored as-is. When omitted, the server generates a UUID handle. When the plan is created from a SubscriptionPlan YAML artifact instead, the handle is always taken from `metadata.name`.| +|refId|string|false|none|Platform API subscription plan UUID to associate with this plan.| +|displayName|string|true|none|none| +|description|string|false|none|none| +|limits|[object]|false|none|Rate/quota limits enforced for this plan. Omit or leave empty for an unlimited plan. Replaces the whole limit set on update.| +|» limitType|string|false|none|none| +|» limitCount|integer|true|none|Use -1 for unlimited, otherwise a positive number.| +|» timeUnit|string¦null|false|none|Omit for a limit with no time window.| +|» timeAmount|integer|false|none|Size of the time window, in `timeUnit` units.| +|type|string|false|none|Legacy shorthand accepted only via SubscriptionPlan/SubscriptionPlanList YAML upload (`multipart/form-data`); converted into `limits` before storage. Ignored for JSON requests — use `limits` instead.| +|requestCount|any|false|none|Legacy YAML shorthand paired with `type: requestcount`. Use -1 for unlimited.| + +oneOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» *anonymous*|integer|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» *anonymous*|string|false|none|none| + +continued + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|eventCount|any|false|none|Legacy YAML shorthand paired with `type: eventcount`. Use -1 for unlimited.| + +oneOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» *anonymous*|integer|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» *anonymous*|string|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|limitType|REQUEST_COUNT| +|limitType|EVENT_COUNT| +|limitType|BANDWIDTH| +|limitType|TOTAL_TOKEN_COUNT| +|timeUnit|MINUTE| +|timeUnit|HOUR| +|timeUnit|DAY| +|timeUnit|MONTH| +|timeUnit|null| +|type|requestcount| +|type|eventcount| + +<h2 id="tocS_LabelRequest">LabelRequest</h2> + +<a id="schemalabelrequest"></a> +<a id="schema_LabelRequest"></a> +<a id="tocSlabelrequest"></a> +<a id="tocslabelrequest"></a> + +```json +{ + "id": "premium", + "displayName": "Premium APIs" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|none|Desired handle for the label (unique per org), stored as-is.| +|displayName|string|true|none|none| + +<h2 id="tocS_ApplicationRequest">ApplicationRequest</h2> + +<a id="schemaapplicationrequest"></a> +<a id="schema_ApplicationRequest"></a> +<a id="tocSapplicationrequest"></a> +<a id="tocsapplicationrequest"></a> + +```json +{ + "displayName": "Weather App", + "id": "my-weather-app", + "description": "Application used to call Weather APIs." +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|displayName|string|true|none|none| +|id|string|false|none|Immutable, org-scoped slug for the application, stored as its handle. Optional — defaults to the application's `displayName` when omitted.| +|description|string|true|none|none| + +<h2 id="tocS_SubscriptionCreateRequest">SubscriptionCreateRequest</h2> + +<a id="schemasubscriptioncreaterequest"></a> +<a id="schema_SubscriptionCreateRequest"></a> +<a id="tocSsubscriptioncreaterequest"></a> +<a id="tocssubscriptioncreaterequest"></a> + +```json +{ + "artifactId": "weather-api-v1", + "subscriptionPlanId": "Gold" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|artifactId|string|true|none|API ID.| +|subscriptionPlanId|string|true|none|API Portal subscription plan ID.| + +<h2 id="tocS_SubscriptionUpdateRequest">SubscriptionUpdateRequest</h2> + +<a id="schemasubscriptionupdaterequest"></a> +<a id="schema_SubscriptionUpdateRequest"></a> +<a id="tocSsubscriptionupdaterequest"></a> +<a id="tocssubscriptionupdaterequest"></a> + +```json +{ + "status": "ACTIVE" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|status|string|true|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|ACTIVE| +|status|INACTIVE| + +<h2 id="tocS_SubscriptionChangePlanRequest">SubscriptionChangePlanRequest</h2> + +<a id="schemasubscriptionchangeplanrequest"></a> +<a id="schema_SubscriptionChangePlanRequest"></a> +<a id="tocSsubscriptionchangeplanrequest"></a> +<a id="tocssubscriptionchangeplanrequest"></a> + +```json +{ + "artifactId": "weather-api-v1", + "planId": "Gold" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|artifactId|string|false|none|API ID the subscription belongs to. Optional — if provided, it is validated against the API derived from the existing subscription record and the request is rejected with 400 if they don't match. It is never used as a fallback: if the API cannot be derived from the subscription record, the request fails with 400 regardless of this value.| +|planId|string|true|none|API Portal subscription plan ID to switch to.| + +<h2 id="tocS_SubscriptionResponse">SubscriptionResponse</h2> + +<a id="schemasubscriptionresponse"></a> +<a id="schema_SubscriptionResponse"></a> +<a id="tocSsubscriptionresponse"></a> +<a id="tocssubscriptionresponse"></a> + +```json +{ + "subscriptionId": "sub-12345", + "artifactId": "weather-api-v1", + "subscriptionToken": "a3f1e8b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1", + "subscriptionPlanName": "Gold", + "status": "ACTIVE", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +Subscription payload. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|subscriptionId|string|false|none|none| +|artifactId|string|false|none|API ID.| +|subscriptionToken|string¦null|false|none|Plaintext subscription token, decrypted on every read (not just on create). Null if decryption fails (e.g. the encryption key changed since the token was stored).| +|subscriptionPlanName|string|false|none|none| +|status|string|false|none|none| +|createdBy|string|false|none|Identity of the user who created the subscription, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|updatedBy|string|false|none|Identity of the user who last updated the subscription, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|ACTIVE| +|status|INACTIVE| + +<h2 id="tocS_ApiKeyRequest">ApiKeyRequest</h2> + +<a id="schemaapikeyrequest"></a> +<a id="schema_ApiKeyRequest"></a> +<a id="tocSapikeyrequest"></a> +<a id="tocsapikeyrequest"></a> + +```json +{ + "id": "weather_prod_key", + "displayName": "Weather Prod Key", + "subscriptionId": "sub-abc123", + "appId": "my-weather-app", + "expiresAt": "2026-12-31T23:59:59Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Optional handle for the key. When provided it must match the pattern and be unique for this API; when omitted, the server generates a UUID handle.| +|displayName|string|false|none|Optional human-readable name for the key. Defaults to `id` when omitted.| +|subscriptionId|string|false|none|Optional subscription ID to associate the key with.| +|appId|string|false|none|Optional application ID to associate the key with, for analytics attribution only — it has no effect on the key's validity or authorization. Must belong to the same organization and be owned by the caller.| +|expiresAt|any|false|none|Optional ISO-8601 datetime with timezone, epoch seconds, or epoch milliseconds.| + +oneOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» *anonymous*|string(date-time)|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» *anonymous*|number|false|none|none| + +<h2 id="tocS_ApiKeyMetadataResponse">ApiKeyMetadataResponse</h2> + +<a id="schemaapikeymetadataresponse"></a> +<a id="schema_ApiKeyMetadataResponse"></a> +<a id="tocSapikeymetadataresponse"></a> +<a id="tocsapikeymetadataresponse"></a> + +```json +{ + "id": "weather_prod_key", + "displayName": "Weather Prod Key", + "apiId": "weather-api-v1", + "appId": "my-weather-app", + "appDisplayName": "My Mobile App", + "status": "ACTIVE", + "expiresAt": "2026-12-31T23:59:59Z", + "createdAt": "2019-08-24T14:15:22Z", + "revokedAt": "2019-08-24T14:15:22Z" +} + +``` + +API key metadata returned by list operations. Secret material is omitted. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|none| +|displayName|string|false|none|none| +|apiId|string|false|none|API ID the key belongs to.| +|appId|string¦null|false|none|ID of the application this key is associated with, if any. Analytics attribution only.| +|appDisplayName|string¦null|false|none|Display name of the associated application, if any.| +|status|string|false|none|none| +|expiresAt|string(date-time)¦null|false|none|none| +|createdAt|string(date-time)|false|none|none| +|revokedAt|string(date-time)¦null|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|ACTIVE| +|status|REVOKED| + +<h2 id="tocS_ApiKeyResponse">ApiKeyResponse</h2> + +<a id="schemaapikeyresponse"></a> +<a id="schema_ApiKeyResponse"></a> +<a id="tocSapikeyresponse"></a> +<a id="tocsapikeyresponse"></a> + +```json +{ + "id": "weather_prod_key", + "displayName": "Weather Prod Key", + "key": "ak_dGhpcyBpcyBub3QgYSByZWFsIGtleQ", + "expiresAt": "2026-12-31T23:59:59Z", + "status": "ACTIVE" +} + +``` + +API key response returned by generate/regenerate only. Unlike ApiKeyMetadataResponse, this does not include apiId, appId, appDisplayName, createdAt, or revokedAt — generate/regenerate return only these five fields. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|none| +|displayName|string|false|none|none| +|key|string|false|none|One-time plaintext API key secret.| +|expiresAt|string(date-time)¦null|false|none|none| +|status|string|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|ACTIVE| +|status|REVOKED| + +<h2 id="tocS_ApiKeyApplicationResponse">ApiKeyApplicationResponse</h2> + +<a id="schemaapikeyapplicationresponse"></a> +<a id="schema_ApiKeyApplicationResponse"></a> +<a id="tocSapikeyapplicationresponse"></a> +<a id="tocsapikeyapplicationresponse"></a> + +```json +{ + "application": { + "id": "my-weather-app", + "displayName": "My Mobile App" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|application|object|false|none|none| +|» id|string|false|none|none| +|» displayName|string|false|none|none| + +<h2 id="tocS_KeyManagerRequest">KeyManagerRequest</h2> + +<a id="schemakeymanagerrequest"></a> +<a id="schema_KeyManagerRequest"></a> +<a id="tocSkeymanagerrequest"></a> +<a id="tocskeymanagerrequest"></a> + +```json +{ + "displayName": "Asgardeo", + "id": "asgardeo-prod", + "enabled": true, + "tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|displayName|string|true|none|none| +|id|string|false|none|Optional desired handle for the key manager (unique per org), stored as-is. When omitted, the server generates a UUID handle. A collision on a handle you supply yourself is reported as 409.| +|enabled|boolean|false|none|none| +|tokenEndpoint|string(uri)|true|none|OAuth2 token endpoint. The OAuth application itself must be created directly in this key manager; the portal only proxies `client_appKeyMappings` token requests to this endpoint.| + +<h2 id="tocS_KeyManagerUpdateRequest">KeyManagerUpdateRequest</h2> + +<a id="schemakeymanagerupdaterequest"></a> +<a id="schema_KeyManagerUpdateRequest"></a> +<a id="tocSkeymanagerupdaterequest"></a> +<a id="tocskeymanagerupdaterequest"></a> + +```json +{ + "displayName": "Asgardeo", + "id": "asgardeo-prod", + "enabled": true, + "tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token" +} + +``` + +Partial update payload for a key manager. All fields are optional; only supplied fields are applied. Omitted fields retain their stored values. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|displayName|string|false|none|none| +|id|string|false|none|Desired handle for the key manager (unique per org), stored as-is.| +|enabled|boolean|false|none|none| +|tokenEndpoint|string(uri)|false|none|none| + +<h2 id="tocS_KeyManagerResponseSchema">KeyManagerResponseSchema</h2> + +<a id="schemakeymanagerresponseschema"></a> +<a id="schema_KeyManagerResponseSchema"></a> +<a id="tocSkeymanagerresponseschema"></a> +<a id="tocskeymanagerresponseschema"></a> + +```json +{ + "id": "asgardeo-prod", + "displayName": "Asgardeo", + "orgId": "org-12345", + "enabled": true, + "tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +Key manager configuration. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|The key manager's handle (unique per org). Not the internal database uuid.| +|displayName|string|false|none|none| +|orgId|string|false|none|none| +|enabled|boolean|false|none|none| +|tokenEndpoint|string(uri)|false|none|none| +|createdBy|string|false|none|Identity of the user who created this key manager, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|updatedBy|string|false|none|Identity of the user who last updated this key manager, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| + +<h2 id="tocS_KeyManagerPublicResponseSchema">KeyManagerPublicResponseSchema</h2> + +<a id="schemakeymanagerpublicresponseschema"></a> +<a id="schema_KeyManagerPublicResponseSchema"></a> +<a id="tocSkeymanagerpublicresponseschema"></a> +<a id="tocskeymanagerpublicresponseschema"></a> + +```json +{ + "id": "asgardeo-prod", + "displayName": "Asgardeo", + "tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token" +} + +``` + +Minimal developer-facing key manager view. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|The key manager's handle (unique per org). Not the internal database uuid.| +|displayName|string|false|none|none| +|tokenEndpoint|string(uri)|false|none|none| + +<h2 id="tocS_WebhookSubscriberRequest">WebhookSubscriberRequest</h2> + +<a id="schemawebhooksubscriberrequest"></a> +<a id="schema_WebhookSubscriberRequest"></a> +<a id="tocSwebhooksubscriberrequest"></a> +<a id="tocswebhooksubscriberrequest"></a> + +```json +{ + "id": "production-gateway", + "displayName": "Production Gateway", + "targetUrl": "https://gateway.example.com/api-portal-webhook", + "secret": "<shared-secret>", + "events": [ + "apikey.*", + "subscription.*" + ], + "enabled": true, + "timeoutMs": 5000 +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Optional handle for the webhook subscriber (unique per org), stored as-is. When omitted, the server generates a UUID handle. Supply it only when you need a specific, stable identifier — it is the id used in the resource path, and a collision on a handle you supplied yourself is reported as 409.| +|displayName|string|true|none|Display name for the webhook subscriber.| +|targetUrl|string(uri)|true|none|Target URL events are POSTed to.| +|secret|string|false|none|Shared secret, used for two purposes. It signs outgoing payloads with a hash-based message authentication code (HMAC-SHA256). It also derives, through HKDF-SHA3-256, the AES-256-GCM key that encrypts sensitive fields in `apikey.*` and `subscription.*` payloads, so only the subscriber can read the plaintext. Encrypted fields arrive as `data.iv`, `data.tag`, and `data.ciphertext`; the derivation and a worked decryption example are documented in the webhook event catalog. Stored encrypted, and never returned in responses.| +|events|[string]|false|none|Glob-style event type allowlist (only a trailing `*` wildcard is supported, e.g. `apikey.*`). Omit or leave empty to receive all event types.| +|enabled|boolean|false|none|none| +|timeoutMs|integer|false|none|none| + +<h2 id="tocS_WebhookSubscriberResponseSchema">WebhookSubscriberResponseSchema</h2> + +<a id="schemawebhooksubscriberresponseschema"></a> +<a id="schema_WebhookSubscriberResponseSchema"></a> +<a id="tocSwebhooksubscriberresponseschema"></a> +<a id="tocswebhooksubscriberresponseschema"></a> + +```json +{ + "id": "production-gateway", + "orgId": "org-12345", + "displayName": "Production Gateway", + "targetUrl": "https://gateway.example.com/api-portal-webhook", + "enabled": true, + "events": [ + "apikey.*", + "subscription.*" + ], + "timeoutMs": 5000, + "hasSecret": true, + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +Webhook subscriber configuration. The secret is never included. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|The webhook subscriber's handle (unique per org). Not the internal database uuid.| +|orgId|string|false|none|none| +|displayName|string|false|none|none| +|targetUrl|string(uri)|false|none|none| +|enabled|boolean|false|none|none| +|events|[string]|false|none|none| +|timeoutMs|integer|false|none|none| +|hasSecret|boolean|false|none|Whether a secret is configured. The same secret serves two purposes. It signs outgoing payloads with a hash-based message authentication code (HMAC), and it derives the AES-256-GCM key that encrypts sensitive fields. Encrypted fields arrive as `data.iv`, `data.tag`, and `data.ciphertext`; the derivation and a worked decryption example are documented in the webhook event catalog.| +|createdBy|string|false|none|Identity of the user who created this webhook subscriber, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|updatedBy|string|false|none|Identity of the user who last updated this webhook subscriber, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| + +<h2 id="tocS_WebhookSubscriberDeliverySummary">WebhookSubscriberDeliverySummary</h2> + +<a id="schemawebhooksubscriberdeliverysummary"></a> +<a id="schema_WebhookSubscriberDeliverySummary"></a> +<a id="tocSwebhooksubscriberdeliverysummary"></a> +<a id="tocswebhooksubscriberdeliverysummary"></a> + +```json +{ + "deliveryId": "del-abc123", + "eventType": "apikey.generated", + "occurredAt": "2019-08-24T14:15:22Z", + "status": "DELIVERED", + "lastHttpStatus": 200, + "lastError": "string", + "lastAttemptAt": "2019-08-24T14:15:22Z", + "deliveredAt": "2019-08-24T14:15:22Z" +} + +``` + +A single delivery attempt made to a webhook subscriber. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|deliveryId|string|false|none|none| +|eventType|string¦null|false|none|none| +|occurredAt|string(date-time)¦null|false|none|none| +|status|string|false|none|none| +|lastHttpStatus|integer¦null|false|none|none| +|lastError|string¦null|false|none|none| +|lastAttemptAt|string(date-time)¦null|false|none|none| +|deliveredAt|string(date-time)¦null|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|PENDING| +|status|IN_FLIGHT| +|status|DELIVERED| +|status|FAILED| + +<h2 id="tocS_AppKeyMappingRequest">AppKeyMappingRequest</h2> + +<a id="schemaappkeymappingrequest"></a> +<a id="schema_AppKeyMappingRequest"></a> +<a id="tocSappkeymappingrequest"></a> +<a id="tocsappkeymappingrequest"></a> + +```json +{ + "keyManager": "Resident Key Manager", + "type": "PRODUCTION", + "consumerKey": "consumer-key-123" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|keyManager|string|true|none|none| +|type|string|false|none|none| +|consumerKey|string|true|none|The OAuth client_id, created directly in the key manager. The portal does not store or persist the client secret — it is supplied per-request when generating a token and is only seen transiently during that request.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|type|PRODUCTION| +|type|SANDBOX| + +<h2 id="tocS_ViewCreateRequest">ViewCreateRequest</h2> + +<a id="schemaviewcreaterequest"></a> +<a id="schema_ViewCreateRequest"></a> +<a id="tocSviewcreaterequest"></a> +<a id="tocsviewcreaterequest"></a> + +```json +{ + "id": "partner-apis", + "displayName": "Partner APIs", + "labels": [ + "partner", + "public" + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|none|Desired handle for the view (unique per org), stored as-is.| +|displayName|string|false|none|Optional display name. Defaults to the handle when omitted.| +|labels|[string]|true|none|Label names to attach to the view.| + +<h2 id="tocS_ViewUpdateRequest">ViewUpdateRequest</h2> + +<a id="schemaviewupdaterequest"></a> +<a id="schema_ViewUpdateRequest"></a> +<a id="tocSviewupdaterequest"></a> +<a id="tocsviewupdaterequest"></a> + +```json +{ + "displayName": "Partner and Public APIs", + "labels": [ + "partner", + "premium" + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|displayName|string|false|none|none| +|labels|[string]|false|none|Full desired set of label names for the view. Labels present here but not currently attached are attached; labels currently attached but absent here are detached. Omit to leave labels unchanged.| + +<h2 id="tocS_OAuthGenerateTokenRequest">OAuthGenerateTokenRequest</h2> + +<a id="schemaoauthgeneratetokenrequest"></a> +<a id="schema_OAuthGenerateTokenRequest"></a> +<a id="tocSoauthgeneratetokenrequest"></a> +<a id="tocsoauthgeneratetokenrequest"></a> + +```json +{ + "consumerSecret": "my-consumer-secret", + "scopes": [ + "weather.read" + ], + "validityPeriod": 3600 +} + +``` + +OAuth access token generation payload. `consumerSecret` is required — the portal uses it to call the Authorization Server token endpoint directly. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|consumerSecret|string|true|none|Client secret for the OAuth application. Not stored by the portal — the caller must supply it on each token generation request.| +|scopes|[string]|false|none|none| +|validityPeriod|integer|false|none|none| + +<h2 id="tocS_ApplicationOAuthKeyResponse">ApplicationOAuthKeyResponse</h2> + +<a id="schemaapplicationoauthkeyresponse"></a> +<a id="schema_ApplicationOAuthKeyResponse"></a> +<a id="tocSapplicationoauthkeyresponse"></a> +<a id="tocsapplicationoauthkeyresponse"></a> + +```json +{ + "keyMappingId": "km-12345", + "keyManager": "Resident Key Manager", + "type": "PRODUCTION", + "consumerKey": "consumer-key-123", + "tokenEndpoint": "https://api.asgardeo.io/t/myorg/oauth2/token" +} + +``` + +OAuth key mapping payload. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|keyMappingId|string|false|none|none| +|keyManager|string|false|none|none| +|type|string|false|none|none| +|consumerKey|string|false|none|none| +|tokenEndpoint|string(uri)|false|none|none| + +<h2 id="tocS_OAuthTokenResponse">OAuthTokenResponse</h2> + +<a id="schemaoauthtokenresponse"></a> +<a id="schema_OAuthTokenResponse"></a> +<a id="tocSoauthtokenresponse"></a> +<a id="tocsoauthtokenresponse"></a> + +```json +{ + "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.example", + "validityTime": 3600, + "tokenScopes": [ + "weather.read" + ] +} + +``` + +Access token response proxied from the key manager's token endpoint. Field names are the portal's own camelCase, not the underlying OAuth2 token response's snake_case. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|accessToken|string|false|none|none| +|validityTime|integer¦null|false|none|Token lifetime in seconds, as reported by the key manager (`expires_in`).| +|tokenScopes|[string]|false|none|none| + +<h2 id="tocS_APIWorkflowCreateResponse">APIWorkflowCreateResponse</h2> + +<a id="schemaapiworkflowcreateresponse"></a> +<a id="schema_APIWorkflowCreateResponse"></a> +<a id="tocSapiworkflowcreateresponse"></a> +<a id="tocsapiworkflowcreateresponse"></a> + +```json +{ + "id": "workflow-12345", + "displayName": "Weather onboarding", + "status": "PUBLISHED" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|none| +|displayName|string|false|none|none| +|status|string|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|DRAFT| +|status|PUBLISHED| + +<h2 id="tocS_APIWorkflowResponse">APIWorkflowResponse</h2> + +<a id="schemaapiworkflowresponse"></a> +<a id="schema_APIWorkflowResponse"></a> +<a id="tocSapiworkflowresponse"></a> +<a id="tocsapiworkflowresponse"></a> + +```json +{ + "id": "workflow-12345", + "displayName": "Weather onboarding", + "description": "string", + "agentPrompt": "string", + "status": "PUBLISHED", + "agentVisibility": "VISIBLE", + "contentType": "ARAZZO", + "apiWorkflowDefinition": "string", + "markdownContent": "string", + "createdAt": "May 7, 2026", + "updatedAt": "string", + "createdBy": "string", + "updatedBy": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|The workflow's handle (unique per org and view). Not the internal database uuid.| +|displayName|string|false|none|none| +|description|string|false|none|none| +|agentPrompt|string|false|none|none| +|status|string|false|none|none| +|agentVisibility|string|false|none|none| +|contentType|string|false|none|none| +|apiWorkflowDefinition|string¦null|false|none|none| +|markdownContent|string¦null|false|none|none| +|createdAt|string|false|none|none| +|updatedAt|string¦null|false|none|none| +|createdBy|string¦null|false|none|none| +|updatedBy|string¦null|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|DRAFT| +|status|PUBLISHED| +|agentVisibility|VISIBLE| +|agentVisibility|HIDDEN| +|contentType|ARAZZO| +|contentType|MD| + +<h2 id="tocS_APIWorkflowPromptResponse">APIWorkflowPromptResponse</h2> + +<a id="schemaapiworkflowpromptresponse"></a> +<a id="schema_APIWorkflowPromptResponse"></a> +<a id="tocSapiworkflowpromptresponse"></a> +<a id="tocsapiworkflowpromptresponse"></a> + +```json +{ + "agentPrompt": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|agentPrompt|string|false|none|none| + +<h2 id="tocS_APIWorkflowCreateRequest">APIWorkflowCreateRequest</h2> + +<a id="schemaapiworkflowcreaterequest"></a> +<a id="schema_APIWorkflowCreateRequest"></a> +<a id="tocSapiworkflowcreaterequest"></a> +<a id="tocsapiworkflowcreaterequest"></a> + +```json +{ + "displayName": "Weather onboarding", + "id": "weather-onboarding", + "description": "Guides users through the Weather API onboarding workflow.", + "agentPrompt": "Follow this workflow to onboard a Weather API user.", + "status": "PUBLISHED", + "agentVisibility": "VISIBLE", + "contentType": "ARAZZO", + "apiWorkflowDefinition": {}, + "markdownContent": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|displayName|string|true|none|none| +|id|string|false|none|Desired handle for the workflow (unique per org and view), stored as-is.| +|description|string|true|none|none| +|agentPrompt|string|false|none|none| +|status|string|false|none|none| +|agentVisibility|string|false|none|none| +|contentType|string|false|none|none| +|apiWorkflowDefinition|any|false|none|JSON/YAML Arazzo content when `contentType` is `ARAZZO`.| + +oneOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» *anonymous*|object|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» *anonymous*|string|false|none|none| + +continued + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|markdownContent|string|false|none|Markdown content when `contentType` is `MD`.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|DRAFT| +|status|PUBLISHED| +|agentVisibility|VISIBLE| +|agentVisibility|HIDDEN| +|contentType|ARAZZO| +|contentType|MD| + +<h2 id="tocS_APIWorkflowUpdateRequest">APIWorkflowUpdateRequest</h2> + +<a id="schemaapiworkflowupdaterequest"></a> +<a id="schema_APIWorkflowUpdateRequest"></a> +<a id="tocSapiworkflowupdaterequest"></a> +<a id="tocsapiworkflowupdaterequest"></a> + +```json +{ + "displayName": "Weather onboarding v2", + "id": "weather-onboarding-v2", + "description": "Updated Weather API onboarding workflow.", + "agentPrompt": "string", + "status": "PUBLISHED", + "agentVisibility": "VISIBLE", + "contentType": "ARAZZO", + "apiWorkflowDefinition": {}, + "markdownContent": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|displayName|string|false|none|none| +|id|string|false|none|Desired handle for the workflow (unique per org and view), stored as-is.| +|description|string|false|none|none| +|agentPrompt|string|false|none|none| +|status|string|false|none|none| +|agentVisibility|string|false|none|none| +|contentType|string|false|none|none| +|apiWorkflowDefinition|any|false|none|none| + +oneOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» *anonymous*|object|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» *anonymous*|string|false|none|none| + +continued + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|markdownContent|string|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|DRAFT| +|status|PUBLISHED| +|agentVisibility|VISIBLE| +|agentVisibility|HIDDEN| +|contentType|ARAZZO| +|contentType|MD| + +<h2 id="tocS_APIWorkflowPromptRequest">APIWorkflowPromptRequest</h2> + +<a id="schemaapiworkflowpromptrequest"></a> +<a id="schema_APIWorkflowPromptRequest"></a> +<a id="tocSapiworkflowpromptrequest"></a> +<a id="tocsapiworkflowpromptrequest"></a> + +```json +{ + "displayName": "Weather onboarding", + "description": "Guides users through the Weather API onboarding workflow.", + "apis": [ + {} + ], + "orgHandle": "acme", + "viewName": "default", + "id": "weather-onboarding" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|displayName|string|true|none|none| +|description|string|true|none|none| +|apis|[object]|false|none|none| +|orgHandle|string|false|none|none| +|viewName|string|false|none|none| +|id|string|false|none|The workflow's (would-be) handle, used only to build the workflow detail URL referenced in the generated prompt.| + +<h2 id="tocS_WebhookEventDelivery">WebhookEventDelivery</h2> + +<a id="schemawebhookeventdelivery"></a> +<a id="schema_WebhookEventDelivery"></a> +<a id="tocSwebhookeventdelivery"></a> +<a id="tocswebhookeventdelivery"></a> + +```json +{ + "deliveryId": "del-abc123", + "subscriberId": "sub-xyz789", + "targetUrl": "https://example.com/webhook", + "status": "DELIVERED", + "lastHttpStatus": 200, + "lastError": "string", + "lastAttemptAt": "2019-08-24T14:15:22Z", + "deliveredAt": "2019-08-24T14:15:22Z" +} + +``` + +A single webhook delivery attempt. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|deliveryId|string|false|none|none| +|subscriberId|string|false|none|none| +|targetUrl|string¦null|false|none|none| +|status|string|false|none|none| +|lastHttpStatus|integer¦null|false|none|none| +|lastError|string¦null|false|none|none| +|lastAttemptAt|string(date-time)¦null|false|none|none| +|deliveredAt|string(date-time)¦null|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|PENDING| +|status|IN_FLIGHT| +|status|DELIVERED| +|status|FAILED| + +<h2 id="tocS_WebhookEvent">WebhookEvent</h2> + +<a id="schemawebhookevent"></a> +<a id="schema_WebhookEvent"></a> +<a id="tocSwebhookevent"></a> +<a id="tocswebhookevent"></a> + +```json +{ + "eventId": "evt-abc123", + "eventType": "apikey.generated", + "orgId": "org-default", + "aggregateType": "apikey", + "aggregateId": "key-12345", + "status": "ALL_DELIVERED", + "occurredAt": "2019-08-24T14:15:22Z", + "deliveries": [ + { + "deliveryId": "del-abc123", + "subscriberId": "sub-xyz789", + "targetUrl": "https://example.com/webhook", + "status": "DELIVERED", + "lastHttpStatus": 200, + "lastError": "string", + "lastAttemptAt": "2019-08-24T14:15:22Z", + "deliveredAt": "2019-08-24T14:15:22Z" + } + ] +} + +``` + +A webhook event with its delivery rows. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|eventId|string|false|none|none| +|eventType|string|false|none|none| +|orgId|string|false|none|none| +|aggregateType|string|false|none|none| +|aggregateId|string|false|none|none| +|status|string|false|none|none| +|occurredAt|string(date-time)|false|none|none| +|deliveries|[[WebhookEventDelivery](#schemawebhookeventdelivery)]|false|none|[A single webhook delivery attempt.]| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|PENDING| +|status|DISPATCHED| +|status|ALL_DELIVERED| +|status|FAILED| diff --git a/en/docs/api-portal/next/rest-api/subscription-plans.md b/en/docs/api-portal/next/rest-api/subscription-plans.md new file mode 100644 index 000000000..a9d33debc --- /dev/null +++ b/en/docs/api-portal/next/rest-api/subscription-plans.md @@ -0,0 +1,712 @@ +--- +title: "Subscription Plans" +description: "List, create, upsert, get, and delete subscription plans via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/subscription-plans/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/subscription-plans.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Subscription Plans + +## List subscription plans + +<a id="opIdlistSubscriptionPlans"></a> + +`GET /subscription-plans` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/subscription-plans \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists subscription plans for an organization. When `name` is supplied, only the matching plan (if any) is returned. Plan names are unique within an organization. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:subscription_plan:read`, `dp:subscription_plan:manage` + +</aside> + +<h3 id="list-subscription-plans-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|name|query|string|false|Filter by exact plan name. Returns an array of zero or one items.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "string", + "displayName": "string", + "description": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "limitCount": 10000, + "timeUnit": "MONTH", + "timeAmount": 1 + } + ], + "refId": "string", + "orgId": "string", + "createdBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" + } + ], + "count": 1, + "pagination": { + "total": 42, + "limit": 20, + "offset": 0 + } +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-subscription-plans-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of subscription plan DTOs. Empty array when no plans match.|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-subscription-plans-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[SubscriptionPlanResponse](schemas.md#schemasubscriptionplanresponse)]|false|none|none| +|»» id|string|false|none|The plan's handle (unique per org). Not the internal database uuid.| +|»» displayName|string|false|none|none| +|»» description|string|false|none|none| +|»» limits|[object]|false|none|Rate/quota limits enforced for this plan. Empty when the plan is unlimited.| +|»»» limitType|string|false|none|none| +|»»» limitCount|any|false|none|Returned as a string when the stored count exceeds the safe integer range, otherwise a number. Unlimited plans have no limit entries — the `limits` array is empty.| + +*oneOf* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»»»» *anonymous*|integer|false|none|none| + +*xor* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»»»» *anonymous*|string|false|none|none| + +*continued* + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|»»» timeUnit|string¦null|false|none|none| +|»»» timeAmount|integer|false|none|none| +|»» refId|string¦null|false|none|Platform API subscription plan UUID associated with this plan.| +|»» orgId|string|false|none|none| +|»» createdBy|string|false|none|Identity of the user who created this subscription plan, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|»» updatedBy|string|false|none|Identity of the user who last updated this subscription plan, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|»» createdAt|string(date-time)|false|none|none| +|»» updatedAt|string(date-time)|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|limitType|REQUEST_COUNT| +|limitType|EVENT_COUNT| +|limitType|BANDWIDTH| +|limitType|TOTAL_TOKEN_COUNT| +|timeUnit|MINUTE| +|timeUnit|HOUR| +|timeUnit|DAY| +|timeUnit|MONTH| +|timeUnit|null| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Create subscription plans + +<a id="opIdaddSubscriptionPlans"></a> + +`POST /subscription-plans` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/subscription-plans \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates one subscription plan when the request body is an object, or multiple subscription plans when the body is an array. Bulk creation returns a message instead of creating plans when `generateDefaultSubPlans` is enabled. + +> Payload + +```json +{ + "id": "Gold", + "refId": "string", + "displayName": "string", + "description": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "limitCount": 10000, + "timeUnit": "MINUTE", + "timeAmount": 1 + } + ] +} +``` + +```yaml +id: Gold +refId: string +displayName: string +description: string +limits: + - limitType: REQUEST_COUNT + limitCount: 10000 + timeUnit: MINUTE + timeAmount: 1 + +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:subscription_plan:create`, `dp:subscription_plan:manage` + +</aside> + +<h3 id="create-subscription-plans-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|any|true|Subscription plan payload. Send a single object for single create/upsert, or a non-empty array for bulk create/upsert; each object carries its rate/quota rules in `limits`. Alternatively, upload a YAML file in the `subscriptionPlan` multipart field; use `kind: SubscriptionPlan` for a single plan or `kind: SubscriptionPlanList` with an `items` array for bulk operations. YAML uploads may also use the shorthand `type: requestcount` or `type: eventcount` shorthand, which is converted into `limits` before storage.| + +> Example responses +> +> 200 Response + +```json +{ + "message": "string" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="create-subscription-plans-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Subscription plan create/update response for single or bulk operations.|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="create-subscription-plans-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|limitType|REQUEST_COUNT| +|limitType|EVENT_COUNT| +|limitType|BANDWIDTH| +|limitType|TOTAL_TOKEN_COUNT| +|timeUnit|MINUTE| +|timeUnit|HOUR| +|timeUnit|DAY| +|timeUnit|MONTH| +|timeUnit|null| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Upsert subscription plans + +<a id="opIdputSubscriptionPlans"></a> + +`PUT /subscription-plans` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/subscription-plans \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Upserts one subscription plan when the request body is an object, or multiple plans when the body is an array. A single plan update returns `200` when an existing plan is updated and `201` when a new plan is created. Bulk updates return a message when `generateDefaultSubPlans` is enabled. + +> Payload + +```json +{ + "id": "Gold", + "refId": "string", + "displayName": "string", + "description": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "limitCount": 10000, + "timeUnit": "MINUTE", + "timeAmount": 1 + } + ] +} +``` + +```yaml +id: Gold +refId: string +displayName: string +description: string +limits: + - limitType: REQUEST_COUNT + limitCount: 10000 + timeUnit: MINUTE + timeAmount: 1 + +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:subscription_plan:update`, `dp:subscription_plan:manage` + +</aside> + +<h3 id="upsert-subscription-plans-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|any|true|Subscription plan payload. Send a single object for single create/upsert, or a non-empty array for bulk create/upsert; each object carries its rate/quota rules in `limits`. Alternatively, upload a YAML file in the `subscriptionPlan` multipart field; use `kind: SubscriptionPlan` for a single plan or `kind: SubscriptionPlanList` with an `items` array for bulk operations. YAML uploads may also use the shorthand `type: requestcount` or `type: eventcount` shorthand, which is converted into `limits` before storage.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "string", + "displayName": "string", + "description": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "limitCount": 10000, + "timeUnit": "MINUTE", + "timeAmount": 1 + } + ], + "refId": "string", + "orgId": "string", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="upsert-subscription-plans-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Subscription plan update response. Bulk updates may return a list, and some configurations return a message.|Inline| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Subscription plan create/update response for single or bulk operations.|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="upsert-subscription-plans-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|limitType|REQUEST_COUNT| +|limitType|EVENT_COUNT| +|limitType|BANDWIDTH| +|limitType|TOTAL_TOKEN_COUNT| +|timeUnit|MINUTE| +|timeUnit|HOUR| +|timeUnit|DAY| +|timeUnit|MONTH| +|timeUnit|null| + +#### Enumerated Values + +|Property|Value| +|---|---| +|limitType|REQUEST_COUNT| +|limitType|EVENT_COUNT| +|limitType|BANDWIDTH| +|limitType|TOTAL_TOKEN_COUNT| +|timeUnit|MINUTE| +|timeUnit|HOUR| +|timeUnit|DAY| +|timeUnit|MONTH| +|timeUnit|null| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Get a subscription plan + +<a id="opIdgetSubscriptionPlan"></a> + +`GET /subscription-plans/{planId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/subscription-plans/{planId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a single subscription plan by `planId`. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:subscription_plan:read`, `dp:subscription_plan:manage` + +</aside> + +<h3 id="get-a-subscription-plan-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|planId|path|string|true|The subscription plan's handle (unique per org).| + +> Example responses +> +> 200 Response + +```json +{ + "id": "string", + "displayName": "string", + "description": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "limitCount": 10000, + "timeUnit": "MINUTE", + "timeAmount": 1 + } + ], + "refId": "string", + "orgId": "string", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-a-subscription-plan-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Subscription plan DTO.|[SubscriptionPlanResponse](schemas.md#schemasubscriptionplanresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="get-a-subscription-plan-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Delete a subscription plan + +<a id="opIddeleteSubscriptionPlan"></a> + +`DELETE /subscription-plans/{planId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/subscription-plans/{planId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes a subscription plan by `planId`. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:subscription_plan:delete`, `dp:subscription_plan:manage` + +</aside> + +<h3 id="delete-a-subscription-plan-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|planId|path|string|true|The subscription plan's handle (unique per org).| + +> Example responses +> +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="delete-a-subscription-plan-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Subscription plan deleted successfully.|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="delete-a-subscription-plan-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| diff --git a/en/docs/api-portal/next/rest-api/subscriptions.md b/en/docs/api-portal/next/rest-api/subscriptions.md new file mode 100644 index 000000000..74354b486 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/subscriptions.md @@ -0,0 +1,724 @@ +--- +title: "Subscriptions" +description: "Create, list, get, update, delete, change plan, and regenerate the token for a subscription via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/subscriptions/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/subscriptions.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Subscriptions + +## Create a subscription + +<a id="opIdcreateSubscription"></a> + +`POST /subscriptions` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/subscriptions \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates a subscription for an API. The API must exist in the API Portal and have subscription plans enabled. The subscription is owned by the authenticated user. + +> Payload + +```json +{ + "artifactId": "weather-api-v1", + "subscriptionPlanId": "Gold" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:subscription:create`, `dp:subscription:manage` + +</aside> + +<h3 id="create-a-subscription-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[SubscriptionCreateRequest](schemas.md#schemasubscriptioncreaterequest)|true|Subscription creation payload. `artifactId` is the API ID.| + +> Example responses +> +> 201 Response + +```json +{ + "subscriptionId": "sub-12345", + "artifactId": "weather-api-v1", + "subscriptionToken": "a3f1e8b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1", + "subscriptionPlanName": "Gold", + "status": "ACTIVE", + "createdBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="create-a-subscription-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Subscription DTO.|[SubscriptionResponse](schemas.md#schemasubscriptionresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="create-a-subscription-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the created subscription.| + +## List subscriptions + +<a id="opIdlistSubscriptions"></a> + +`GET /subscriptions` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/subscriptions \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists subscriptions owned by the authenticated user. When `artifactId` is provided, results are additionally filtered by the API ID. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:subscription:read`, `dp:subscription:manage` + +</aside> + +<h3 id="list-subscriptions-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|artifactId|query|string|false|Optional API ID used to filter results.| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "subscriptionId": "sub-12345", + "artifactId": "weather-api-v1", + "subscriptionToken": "a3f1e8b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1", + "subscriptionPlanName": "Gold", + "status": "ACTIVE", + "createdBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" + } + ], + "count": 1, + "pagination": { + "total": 42, + "limit": 20, + "offset": 0 + } +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "RESOURCE_NOT_FOUND", + "message": "API not found" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-subscriptions-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of subscription DTOs.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Returned when `artifactId` is provided but does not match an existing API.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-subscriptions-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[SubscriptionResponse](schemas.md#schemasubscriptionresponse)]|false|none|[Subscription payload.]| +|»» subscriptionId|string|false|none|none| +|»» artifactId|string|false|none|API ID.| +|»» subscriptionToken|string¦null|false|none|Plaintext subscription token, decrypted on every read (not just on create). Null if decryption fails (e.g. the encryption key changed since the token was stored).| +|»» subscriptionPlanName|string|false|none|none| +|»» status|string|false|none|none| +|»» createdBy|string|false|none|Identity of the user who created the subscription, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|»» updatedBy|string|false|none|Identity of the user who last updated the subscription, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|»» createdAt|string(date-time)|false|none|none| +|»» updatedAt|string(date-time)|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|ACTIVE| +|status|INACTIVE| + +## Get a subscription + +<a id="opIdgetSubscription"></a> + +`GET /subscriptions/{subId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/subscriptions/{subId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a single subscription by subscription ID. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:subscription:read`, `dp:subscription:manage` + +</aside> + +<h3 id="get-a-subscription-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|subId|path|string|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "subscriptionId": "sub-12345", + "artifactId": "weather-api-v1", + "subscriptionToken": "a3f1e8b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1", + "subscriptionPlanName": "Gold", + "status": "ACTIVE", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-a-subscription-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Subscription DTO.|[SubscriptionResponse](schemas.md#schemasubscriptionresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Update a subscription + +<a id="opIdupdateSubscription"></a> + +`PUT /subscriptions/{subId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/subscriptions/{subId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates the subscription status. Accepts only `ACTIVE` or `INACTIVE`. + +> Payload + +```json +{ + "status": "ACTIVE" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:subscription:update`, `dp:subscription:manage` + +</aside> + +<h3 id="update-a-subscription-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[SubscriptionUpdateRequest](schemas.md#schemasubscriptionupdaterequest)|true|Subscription status update payload.| +|subId|path|string|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "subscriptionId": "sub-12345", + "artifactId": "weather-api-v1", + "subscriptionToken": "a3f1e8b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1", + "subscriptionPlanName": "Gold", + "status": "ACTIVE", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="update-a-subscription-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Subscription DTO.|[SubscriptionResponse](schemas.md#schemasubscriptionresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="update-a-subscription-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Delete a subscription + +<a id="opIddeleteSubscription"></a> + +`DELETE /subscriptions/{subId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/subscriptions/{subId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes the subscription and returns a success message. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:subscription:delete`, `dp:subscription:manage` + +</aside> + +<h3 id="delete-a-subscription-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|subId|path|string|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "message": "string" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="delete-a-subscription-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Change subscription plan + +<a id="opIdchangePlan"></a> + +`POST /subscriptions/{subId}/change-plan` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/subscriptions/{subId}/change-plan \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Changes the subscription plan in-place. The subscription UUID and token remain unchanged; only the plan is updated. A `subscription.plan_changed` webhook event is published to the organization's configured webhook subscribers. + +> Payload + +```json +{ + "artifactId": "weather-api-v1", + "planId": "Gold" +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:subscription:update`, `dp:subscription:manage` + +</aside> + +<h3 id="change-subscription-plan-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[SubscriptionChangePlanRequest](schemas.md#schemasubscriptionchangeplanrequest)|true|Subscription plan change payload. `planId` is the API Portal subscription plan ID.| +|subId|path|string|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "subscriptionId": "sub-12345", + "artifactId": "weather-api-v1", + "subscriptionToken": "a3f1e8b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1", + "subscriptionPlanName": "Gold", + "status": "ACTIVE", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="change-subscription-plan-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Subscription DTO.|[SubscriptionResponse](schemas.md#schemasubscriptionresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="change-subscription-plan-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Regenerate subscription token + +<a id="opIdregenerateSubscriptionToken"></a> + +`POST /subscriptions/{subId}/regenerate-token` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/subscriptions/{subId}/regenerate-token \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Regenerates the subscription token, immediately invalidating the old one. A `subscription.token_regenerated` webhook event is published to the organization's configured webhook subscribers so they can update the token at the gateway. The new plaintext token is returned in the response. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:subscription:manage` + +</aside> + +<h3 id="regenerate-subscription-token-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|subId|path|string|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "subscriptionId": "sub-12345", + "artifactId": "weather-api-v1", + "subscriptionToken": "a3f1e8b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1b3c5d7e9f10b2c4d6e8f0a1", + "subscriptionPlanName": "Gold", + "status": "ACTIVE", + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="regenerate-subscription-token-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Subscription DTO.|[SubscriptionResponse](schemas.md#schemasubscriptionresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| diff --git a/en/docs/api-portal/next/rest-api/views.md b/en/docs/api-portal/next/rest-api/views.md new file mode 100644 index 000000000..a7e350ca1 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/views.md @@ -0,0 +1,548 @@ +--- +title: "Views" +description: "Create, list, update, and delete views via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/views/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/views.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Views + +## Create a view + +<a id="opIdaddView"></a> + +`POST /views` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/views \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates an API Portal view for an organization and associates it with the supplied label names. If `name` is omitted, the service stores the view's handle as its name. + +> Payload + +```json +{ + "id": "partner-apis", + "displayName": "Partner APIs", + "labels": [ + "partner", + "public" + ] +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:view:create`, `dp:view:manage` + +</aside> + +<h3 id="create-a-view-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[ViewCreateRequest](schemas.md#schemaviewcreaterequest)|true|View creation payload with the label names that should be visible in the view.| + +> Example responses +> +> 201 Response + +```json +{ + "message": "string" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="create-a-view-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|JSON message response.|[MessageResponse](schemas.md#schemamessageresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="create-a-view-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## List views + +<a id="opIdgetAllViews"></a> + +`GET /views` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/views \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists all views configured for the organization. Each view includes the label names attached to it. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:view:read`, `dp:view:manage` + +</aside> + +<h3 id="list-views-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "partner-apis", + "displayName": "Partner APIs", + "labels": [ + "partner", + "public" + ], + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "count": 1, + "pagination": { + "total": 42, + "limit": 20, + "offset": 0 + } +} +``` + +> 404 Response + +``` +"string" +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-views-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of view DTOs.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Plain text success response.|string| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-views-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[ViewResponse](schemas.md#schemaviewresponse)]|false|none|none| +|»» id|string|true|none|The view's handle (unique per org). Not the internal database uuid.| +|»» displayName|string|true|none|none| +|»» labels|[string]|true|none|none| +|»» createdBy|string|false|none|Identity of the user who created this view, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|»» updatedBy|string|false|none|Identity of the user who last updated this view, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|»» createdAt|string(date-time)|false|none|none| +|»» updatedAt|string(date-time)|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +## Update a view + +<a id="opIdupdateView"></a> + +`PUT /views/{viewId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/views/{viewId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates the view's display name, its label associations, or both. When `labels` is supplied, it fully replaces the view's label set — labels present in the list are attached and any others are detached. The service returns the accepted request payload. + +> Payload + +```json +{ + "displayName": "Partner and Public APIs", + "labels": [ + "partner", + "premium" + ] +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:view:update`, `dp:view:manage` + +</aside> + +<h3 id="update-a-view-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[ViewUpdateRequest](schemas.md#schemaviewupdaterequest)|true|View update payload. Include only the display name or label changes that should be applied.| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "displayName": "Partner and Public APIs", + "labels": [ + "partner", + "premium" + ] +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="update-a-view-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Echo of the accepted view update payload.|[ViewUpdateRequest](schemas.md#schemaviewupdaterequest)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="update-a-view-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Get a view + +<a id="opIdgetView"></a> + +`GET /views/{viewId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/views/{viewId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves one view by its `viewId` handle, including the label names attached to that view. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:view:read`, `dp:view:manage` + +</aside> + +<h3 id="get-a-view-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "partner-apis", + "displayName": "Partner APIs", + "labels": [ + "partner", + "public" + ], + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2026-05-07T08:30:00Z", + "updatedAt": "2026-05-07T08:30:00Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +``` +"string" +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-a-view-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|View DTO response.|[ViewResponse](schemas.md#schemaviewresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Plain text success response.|string| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="get-a-view-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Delete a view + +<a id="opIddeleteView"></a> + +`DELETE /views/{viewId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/views/{viewId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes a view by its `viewId` handle. A missing view is returned as a not-found error. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:view:delete`, `dp:view:manage` + +</aside> + +<h3 id="delete-a-view-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|viewId|path|string|true|The view's handle (unique per org). Not the internal database uuid.| + +> Example responses +> +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="delete-a-view-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|View deleted successfully.|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="delete-a-view-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| diff --git a/en/docs/api-portal/next/rest-api/webhook-events.md b/en/docs/api-portal/next/rest-api/webhook-events.md new file mode 100644 index 000000000..5f391c693 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/webhook-events.md @@ -0,0 +1,267 @@ +--- +title: "Webhook Events" +description: "List and get webhook events via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/webhook-events/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/webhook-events.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Webhook Events + +## List webhook events + +<a id="opIdlistWebhookEvents"></a> + +`GET /webhook-events` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/webhook-events \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns a paginated list of webhook events for the organization. Each event includes a summary of its delivery rows. Requires the `dp:event:read` scope. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:event:read` + +</aside> + +<h3 id="list-webhook-events-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|status|query|string|false|Filter events by status.| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| + +#### Enumerated Values + +|Parameter|Value| +|---|---| +|status|PENDING| +|status|DISPATCHED| +|status|ALL_DELIVERED| +|status|FAILED| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "eventId": "evt-abc123", + "eventType": "apikey.generated", + "orgId": "org-default", + "aggregateType": "apikey", + "aggregateId": "key-12345", + "status": "ALL_DELIVERED", + "occurredAt": "2019-08-24T14:15:22Z", + "deliveries": [ + { + "deliveryId": "del-abc123", + "subscriberId": "sub-xyz789", + "targetUrl": "https://example.com/webhook", + "status": "DELIVERED", + "lastHttpStatus": 200, + "lastError": "string", + "lastAttemptAt": "2019-08-24T14:15:22Z", + "deliveredAt": "2019-08-24T14:15:22Z" + } + ] + } + ], + "count": 1, + "pagination": { + "total": 42, + "limit": 20, + "offset": 0 + } +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-webhook-events-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Paginated list of webhook events.|Inline| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-webhook-events-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[WebhookEvent](schemas.md#schemawebhookevent)]|false|none|[A webhook event with its delivery rows.]| +|»» eventId|string|false|none|none| +|»» eventType|string|false|none|none| +|»» orgId|string|false|none|none| +|»» aggregateType|string|false|none|none| +|»» aggregateId|string|false|none|none| +|»» status|string|false|none|none| +|»» occurredAt|string(date-time)|false|none|none| +|»» deliveries|[[WebhookEventDelivery](schemas.md#schemawebhookeventdelivery)]|false|none|[A single webhook delivery attempt.]| +|»»» deliveryId|string|false|none|none| +|»»» subscriberId|string|false|none|none| +|»»» targetUrl|string¦null|false|none|none| +|»»» status|string|false|none|none| +|»»» lastHttpStatus|integer¦null|false|none|none| +|»»» lastError|string¦null|false|none|none| +|»»» lastAttemptAt|string(date-time)¦null|false|none|none| +|»»» deliveredAt|string(date-time)¦null|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|PENDING| +|status|DISPATCHED| +|status|ALL_DELIVERED| +|status|FAILED| +|status|PENDING| +|status|IN_FLIGHT| +|status|DELIVERED| +|status|FAILED| + +## Get a webhook event + +<a id="opIdgetWebhookEvent"></a> + +`GET /webhook-events/{eventId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/webhook-events/{eventId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns a single webhook event with the full details of all its delivery rows. Requires the `dp:event:read` scope. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:event:read` + +</aside> + +<h3 id="get-a-webhook-event-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|eventId|path|string|true|Webhook event identifier.| + +> Example responses +> +> 200 Response + +```json +{ + "eventId": "evt-abc123", + "eventType": "apikey.generated", + "orgId": "org-default", + "aggregateType": "apikey", + "aggregateId": "key-12345", + "status": "ALL_DELIVERED", + "occurredAt": "2019-08-24T14:15:22Z", + "deliveries": [ + { + "deliveryId": "del-abc123", + "subscriberId": "sub-xyz789", + "targetUrl": "https://example.com/webhook", + "status": "DELIVERED", + "lastHttpStatus": 200, + "lastError": "string", + "lastAttemptAt": "2019-08-24T14:15:22Z", + "deliveredAt": "2019-08-24T14:15:22Z" + } + ] +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "Forbidden" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-a-webhook-event-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Single webhook event with full delivery details.|[WebhookEvent](schemas.md#schemawebhookevent)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Request is forbidden. The caller lacks the required permission, or the current runtime mode disallows the operation (read-only mode). It is also returned when the request names an organization other than the single one this instance serves. A nonexistent organization is answered identically to one belonging to someone else, so the response cannot be used to discover what a shared database holds.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| diff --git a/en/docs/api-portal/next/rest-api/webhook-subscribers.md b/en/docs/api-portal/next/rest-api/webhook-subscribers.md new file mode 100644 index 000000000..0de73d420 --- /dev/null +++ b/en/docs/api-portal/next/rest-api/webhook-subscribers.md @@ -0,0 +1,656 @@ +--- +title: "Webhook Subscribers" +description: "Create, list, get, update, delete, and view recent deliveries for webhook subscribers via the API Portal REST API." +canonical_url: https://wso2.com/api-platform/docs/api-portal/rest-api/webhook-subscribers/ +md_url: https://wso2.com/api-platform/docs/api-portal/rest-api/webhook-subscribers.md +tags: + - cloud + - api-portal + - rest-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "reference" +--- + +# Webhook Subscribers + +## Create a webhook subscriber + +<a id="opIdcreateWebhookSubscriber"></a> + +`POST /webhook-subscribers` + +> Code samples + +```shell + +curl -X POST https://localhost:9543/api/v0.9/webhook-subscribers \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Registers a webhook subscriber for the organization. Event deliveries (apikey.*, subscription.*, etc.) matching the subscriber's events filter are fanned out to its target URL. The `secret`, if provided, is encrypted at rest using AES-256-GCM. When `id` is omitted, the server generates a UUID handle; the assigned handle is returned as `id` in the response and is what later requests address. + +> Payload + +```json +{ + "id": "production-gateway", + "displayName": "Production Gateway", + "targetUrl": "https://gateway.example.com/api-portal-webhook", + "secret": "<shared-secret>", + "events": [ + "apikey.*", + "subscription.*" + ], + "enabled": true, + "timeoutMs": 5000 +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:webhook_subscriber:create`, `dp:webhook_subscriber:manage` + +</aside> + +<h3 id="create-a-webhook-subscriber-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[WebhookSubscriberRequest](schemas.md#schemawebhooksubscriberrequest)|true|Webhook subscriber configuration payload.| + +> Example responses +> +> 201 Response + +```json +{ + "id": "production-gateway", + "orgId": "org-12345", + "displayName": "Production Gateway", + "targetUrl": "https://gateway.example.com/api-portal-webhook", + "enabled": true, + "events": [ + "apikey.*", + "subscription.*" + ], + "timeoutMs": 5000, + "hasSecret": true, + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="create-a-webhook-subscriber-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Webhook subscriber configuration response.|[WebhookSubscriberResponseSchema](schemas.md#schemawebhooksubscriberresponseschema)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="create-a-webhook-subscriber-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the created webhook subscriber.| + +## List webhook subscribers + +<a id="opIdgetWebhookSubscribers"></a> + +`GET /webhook-subscribers` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/webhook-subscribers \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns all webhook subscriber configurations for the organization. Secrets are never included in the response. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:webhook_subscriber:read`, `dp:webhook_subscriber:manage` + +</aside> + +<h3 id="list-webhook-subscribers-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of records to return.| +|offset|query|integer|false|Number of records to skip before returning results.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "production-gateway", + "orgId": "org-12345", + "displayName": "Production Gateway", + "targetUrl": "https://gateway.example.com/api-portal-webhook", + "enabled": true, + "events": [ + "apikey.*", + "subscription.*" + ], + "timeoutMs": 5000, + "hasSecret": true, + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "count": 1, + "pagination": { + "total": 42, + "limit": 20, + "offset": 0 + } +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-webhook-subscribers-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of webhook subscriber configurations.|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-webhook-subscribers-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[WebhookSubscriberResponseSchema](schemas.md#schemawebhooksubscriberresponseschema)]|false|none|[Webhook subscriber configuration. The secret is never included.]| +|»» id|string|false|none|The webhook subscriber's handle (unique per org). Not the internal database uuid.| +|»» orgId|string|false|none|none| +|»» displayName|string|false|none|none| +|»» targetUrl|string(uri)|false|none|none| +|»» enabled|boolean|false|none|none| +|»» events|[string]|false|none|none| +|»» timeoutMs|integer|false|none|none| +|»» hasSecret|boolean|false|none|Whether a secret is configured. The same secret serves two purposes. It signs outgoing payloads with a hash-based message authentication code (HMAC), and it derives the AES-256-GCM key that encrypts sensitive fields. Encrypted fields arrive as `data.iv`, `data.tag`, and `data.ciphertext`; the derivation and a worked decryption example are documented in the webhook event catalog.| +|»» createdBy|string|false|none|Identity of the user who created this webhook subscriber, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses and list items.| +|»» updatedBy|string|false|none|Identity of the user who last updated this webhook subscriber, or `deleted_user` if that user's IDP reference no longer exists. Present on single-resource GET responses only, omitted on list items.| +|»» createdAt|string(date-time)|false|none|none| +|»» updatedAt|string(date-time)|false|none|none| +|» count|integer|false|none|Number of items returned in this page.| +|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.| +|»» total|integer|true|none|Total number of records matching the query.| +|»» limit|integer|true|none|Maximum number of records returned in this response.| +|»» offset|integer|true|none|Number of records skipped before this page.| + +## Get a webhook subscriber + +<a id="opIdgetWebhookSubscriber"></a> + +`GET /webhook-subscribers/{subscriberId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/webhook-subscribers/{subscriberId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a single webhook subscriber configuration by ID. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:webhook_subscriber:read`, `dp:webhook_subscriber:manage` + +</aside> + +<h3 id="get-a-webhook-subscriber-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|subscriberId|path|string|true|The webhook subscriber's handle (its `id` in request/response payloads), not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "production-gateway", + "orgId": "org-12345", + "displayName": "Production Gateway", + "targetUrl": "https://gateway.example.com/api-portal-webhook", + "enabled": true, + "events": [ + "apikey.*", + "subscription.*" + ], + "timeoutMs": 5000, + "hasSecret": true, + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="get-a-webhook-subscriber-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Webhook subscriber configuration response.|[WebhookSubscriberResponseSchema](schemas.md#schemawebhooksubscriberresponseschema)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## Update a webhook subscriber + +<a id="opIdupdateWebhookSubscriber"></a> + +`PUT /webhook-subscribers/{subscriberId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9543/api/v0.9/webhook-subscribers/{subscriberId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates an existing webhook subscriber configuration. Only supplied fields are updated; omitted fields retain their stored values. + +> Payload + +```json +{ + "id": "production-gateway", + "displayName": "Production Gateway", + "targetUrl": "https://gateway.example.com/api-portal-webhook", + "secret": "<shared-secret>", + "events": [ + "apikey.*", + "subscription.*" + ], + "enabled": true, + "timeoutMs": 5000 +} +``` + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:webhook_subscriber:update`, `dp:webhook_subscriber:manage` + +</aside> + +<h3 id="update-a-webhook-subscriber-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[WebhookSubscriberRequest](schemas.md#schemawebhooksubscriberrequest)|false|Webhook subscriber update payload. All fields are optional; only supplied fields are updated.| +|subscriberId|path|string|true|The webhook subscriber's handle (its `id` in request/response payloads), not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "production-gateway", + "orgId": "org-12345", + "displayName": "Production Gateway", + "targetUrl": "https://gateway.example.com/api-portal-webhook", + "enabled": true, + "events": [ + "apikey.*", + "subscription.*" + ], + "timeoutMs": 5000, + "hasSecret": true, + "createdBy": "alice@example.com", + "updatedBy": "alice@example.com", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object. + +```json +{ + "status": "error", + "code": "MISSING_REQUIRED_PARAMETER", + "message": "Missing required parameter." +} +``` + +```json +{ + "message": "Missing or invalid fields in the request payload" +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "Conflict" +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="update-a-webhook-subscriber-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Webhook subscriber configuration response.|[WebhookSubscriberResponseSchema](schemas.md#schemawebhooksubscriberresponseschema)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Validation and other bad-request errors are returned as a standard error object (field-level details, when present, are carried in its `errors` array); some legacy handlers return a message-only object.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|The request conflicts with an existing resource.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="update-a-webhook-subscriber-responseschema">Response schema</h3> + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +## Delete a webhook subscriber + +<a id="opIddeleteWebhookSubscriber"></a> + +`DELETE /webhook-subscribers/{subscriberId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9543/api/v0.9/webhook-subscribers/{subscriberId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes a webhook subscriber configuration by ID. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:webhook_subscriber:delete`, `dp:webhook_subscriber:manage` + +</aside> + +<h3 id="delete-a-webhook-subscriber-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|subscriberId|path|string|true|The webhook subscriber's handle (its `id` in request/response payloads), not the internal database uuid.| + +> Example responses +> +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="delete-a-webhook-subscriber-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Webhook subscriber deleted successfully.|None| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +## List recent deliveries for a webhook subscriber + +<a id="opIdgetWebhookSubscriberDeliveries"></a> + +`GET /webhook-subscribers/{subscriberId}/deliveries` + +> Code samples + +```shell + +curl -X GET https://localhost:9543/api/v0.9/webhook-subscribers/{subscriberId}/deliveries \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns the most recent webhook delivery attempts for a single subscriber, newest first. + +### Authentication + +<aside class="warning"> +This operation requires a <strong>Bearer JWT</strong> access token in the <code>Authorization</code> header. + +Required scopes (the token must carry at least one of): `dp:webhook_subscriber:read`, `dp:webhook_subscriber:manage` + +</aside> + +<h3 id="list-recent-deliveries-for-a-webhook-subscriber-parameters">Parameters</h3> + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|subscriberId|path|string|true|The webhook subscriber's handle (its `id` in request/response payloads), not the internal database uuid.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "deliveryId": "del-abc123", + "eventType": "apikey.generated", + "occurredAt": "2019-08-24T14:15:22Z", + "status": "DELIVERED", + "lastHttpStatus": 200, + "lastError": "string", + "lastAttemptAt": "2019-08-24T14:15:22Z", + "deliveredAt": "2019-08-24T14:15:22Z" + } + ] +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "ORG_NOT_FOUND", + "message": "Organization not found." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_SERVER_ERROR", + "message": "An unexpected error occurred." +} +``` + +<h3 id="list-recent-deliveries-for-a-webhook-subscriber-responses">Responses</h3> + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Recent delivery attempts for this webhook subscriber.|Inline| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Resource not found.|[ErrorResponse](schemas.md#schemaerrorresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error.|[ErrorResponse](schemas.md#schemaerrorresponse)| + +<h3 id="list-recent-deliveries-for-a-webhook-subscriber-responseschema">Response schema</h3> + +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» list|[[WebhookSubscriberDeliverySummary](schemas.md#schemawebhooksubscriberdeliverysummary)]|false|none|[A single delivery attempt made to a webhook subscriber.]| +|»» deliveryId|string|false|none|none| +|»» eventType|string¦null|false|none|none| +|»» occurredAt|string(date-time)¦null|false|none|none| +|»» status|string|false|none|none| +|»» lastHttpStatus|integer¦null|false|none|none| +|»» lastError|string¦null|false|none|none| +|»» lastAttemptAt|string(date-time)¦null|false|none|none| +|»» deliveredAt|string(date-time)¦null|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|PENDING| +|status|IN_FLIGHT| +|status|DELIVERED| +|status|FAILED| diff --git a/en/docs/api-portal/next/setting-up/artifact-types.md b/en/docs/api-portal/next/setting-up/artifact-types.md new file mode 100644 index 000000000..60d3161f9 --- /dev/null +++ b/en/docs/api-portal/next/setting-up/artifact-types.md @@ -0,0 +1,65 @@ +--- +title: "Artifact types" +description: "Choose which artifact types—APIs, Model Context Protocol (MCP) servers, and API workflows—the API Portal & MCP Hub serves, via the api_portal.artifacts table in config.toml." +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/artifact-types/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/artifact-types.md +tags: + - cloud + - api-portal + - configuration +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-31 +content_type: "concept" +--- + +# Artifact types + +The API Portal & MCP Hub can serve three artifact types—**APIs**, **Model Context Protocol (MCP) servers**, and **API workflows**. Which of them a portal serves is set by the operator in `configs/config.toml`, under the `[api_portal.artifacts]` table: + +```toml +[api_portal.artifacts] +enabled_types = ["apis", "mcp-servers", "api-workflows"] +``` + +`enabled_types` is an allowlist. Its valid entries are: + +| Entry | Serves | +|-------|--------| +| `apis` | REST, WebSocket, GraphQL, WebSub, and SOAP APIs | +| `mcp-servers` | MCP servers | +| `api-workflows` | API workflows | + +Any combination is valid. Omit the `[api_portal.artifacts]` section entirely to serve all three—that is the default. + +## How it behaves + +- **A type you leave out disappears completely.** It gets no navigation entry and no landing-page section, and its routes return `404` rather than rendering an empty page. +- **The order you list types in is the order they appear.** Listing `mcp-servers` first shows MCP servers first in the navigation and in the landing-page heading. +- **Configuration errors fail fast.** An unrecognised entry (for example, a typo) aborts startup with a fatal error, so a mistake can't silently drop a type. An empty list starts the portal but leaves it with nothing to browse, and logs a warning. + +## Examples + +An API-only portal: + +```toml +[api_portal.artifacts] +enabled_types = ["apis"] +``` + +An MCP-only hub: + +```toml +[api_portal.artifacts] +enabled_types = ["mcp-servers"] +``` + +APIs and MCP servers, without workflows: + +```toml +[api_portal.artifacts] +enabled_types = ["apis", "mcp-servers"] +``` + +## Checking the current setting + +In the portal's organization settings, the **Configuration** tab shows an **Artifact types served** field. It is read-only—the value comes from `[api_portal.artifacts]` in `config.toml` and can only be changed there. diff --git a/en/docs/api-portal/next/setting-up/authentication/asgardeo-as-idp.md b/en/docs/api-portal/next/setting-up/authentication/asgardeo-as-idp.md new file mode 100644 index 000000000..ace46e5bd --- /dev/null +++ b/en/docs/api-portal/next/setting-up/authentication/asgardeo-as-idp.md @@ -0,0 +1,218 @@ +--- +title: "Set up Asgardeo as your identity provider" +description: "Configure WSO2 Asgardeo as the OIDC identity provider for a production API Portal deployment, from application registration to config.toml." +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/authentication/asgardeo-as-idp/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/authentication/asgardeo-as-idp.md +tags: + - cloud + - api-portal + - tutorials + - authentication +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-04 +content_type: "tutorial" +--- + +# Set up Asgardeo as your identity provider + +This tutorial walks you through configuring WSO2 Asgardeo as the identity provider for a production API Portal deployment. It's one worked example of the general procedure in [Connect an identity provider to the API Portal](connect-an-identity-provider.md)—read that first if you're integrating a different provider. For how identity provider authentication compares with local authentication, see [Authentication in the API Portal & MCP Hub](overview.md). + +The API Portal & MCP Hub uses Asgardeo's sub-organization model: each API Portal organization maps to one Asgardeo sub-organization. A single Asgardeo application, shared across every portal organization, handles login, and each session is scoped to one sub-organization: + +1. A portal organization's handle is the Asgardeo sub-org handle. +2. When a user selects **Login**, the portal redirects to Asgardeo with `org=<handle>`, scoping the authorization to that sub-organization. +3. Asgardeo issues a JWT whose organization claim identifies the sub-organization. On every authenticated request, the portal verifies this claim matches the organization being accessed. +4. Each session is bound to one sub-organization—reaching a different portal organization's protected pages means logging out and back in on that organization. + +## Prerequisites + +- An Asgardeo account at [console.asgardeo.io](https://console.asgardeo.io) +- The API Portal & MCP Hub accessible at a known hostname + +## Step 1: Set up your organization + +1. Log in to [console.asgardeo.io](https://console.asgardeo.io). +2. Create or select your root organization. +3. If you need multiple tenants, create sub-organizations at `https://console.asgardeo.io/t/<root-org>/app/organizations`. + +## Step 2: Register the API Portal application + +The API Portal & MCP Hub is a server-side application that can hold a client secret, so register it as a confidential client. A single-page application is a public client and cannot complete the confidential authorization-code exchange the portal relies on. + +1. In the root organization, go to **Applications > New Application**. +2. Choose **Traditional Web Application** and name it `API Portal & MCP Hub`. +3. Under **Authorized redirect URLs**, add both, replacing `<org-handle>` with the sub-organization handle you settle on in step 5: + - `https://<your-domain>/api-portal/<org-handle>/callback`—the login callback + - `https://<your-domain>/api-portal/<org-handle>`—the post-logout redirect (Asgardeo validates `post_logout_redirect_uri` against this same list) + + This single shared URI pair is the only one you register. It matches the `callback_url` and `logout_redirect_uri` you set in step 4—after the callback, the portal uses the session's stored return path to route the user to the correct organization, so no per-organization redirect URLs are needed. +4. Enable **Share with all organizations** so users in sub-organizations can log in. +5. Under the **Protocol** tab, set **Access Token Type** to **JWT**. +6. Under the **Login Flow** tab, remove the Username/Password authenticator and add **SSO Authentication** (organization SSO), which routes each user to their sub-organization's login experience. +7. Under the **User Attributes** tab, add these attributes to the token: `given_name`, `family_name`, `email`, and `roles`. + +Note the client ID and client secret from the **Protocol** tab. The portal needs both, and the client ID is also used as the audience in the portal configuration. + +## Step 3: Create the two roles + +The portal recognizes two personas: whoever administers it, and whoever consumes APIs through it. In the portal's default `mode = "role"`, a token's role names are expanded into `dp:*` scopes through the portal's own grant table, so Asgardeo only has to emit role names—it never mints a `dp:*` scope. Use the names the shipped grant table already defines: + +| Asgardeo role | Grants in the portal | +|---------------|----------------------| +| `dp_admin` | Every action on the organization: content and theme, the API and MCP server catalog, views, labels, subscription plans, key managers, webhook subscribers, and every application and subscription | +| `dp_subscriber` | The consumer persona: own applications, subscriptions, and keys, plus read access to the catalog | + +1. Open the **API Portal & MCP Hub** application you registered in step 2. +2. Under the **Roles** tab, create an application role named `dp_admin` and another named `dp_subscriber`. +3. Assign `dp_admin` **only to administrators**, and `dp_subscriber` to regular users in each sub-organization that needs access. + +Step 4 points both `[api_portal.auth.authorization.portal_roles]` entries at these same names, so one pair of Asgardeo roles drives both page access and Management API authorization. To change what either role grants, edit the portal's `role-to-scope-mapping.yaml`—see [Choose how privileges reach the token](connect-an-identity-provider.md#step-3-choose-how-privileges-reach-the-token). + +!!! note + Browser login sessions still pass through the per-operation scope check in role mode, which is the gap role mode exists to close: the session's own roles claim is what the portal expands to authorize each Management API request. + +## Step 4: Configure the API Portal & MCP Hub + +Update the `[api_portal.auth]` tables in `configs/config.toml`: + +{% raw %} + +```toml +[api_portal.auth] +mode = "idp" + +[api_portal.auth.idp] +name = "Asgardeo" +issuer = "https://api.asgardeo.io/t/<your-tenant>/oauth2/token" +authorization_url = "https://api.asgardeo.io/t/<your-tenant>/oauth2/authorize" +token_url = "https://api.asgardeo.io/t/<your-tenant>/oauth2/token" +user_info_url = "https://api.asgardeo.io/t/<your-tenant>/oauth2/userinfo" +jwks_url = "https://api.asgardeo.io/t/<your-tenant>/oauth2/jwks" +client_id = "<api-portal-app-client-id>" +client_secret = '{{ env "APIP_AP_AUTH_IDP_CLIENT_SECRET" }}' +audience = "<api-portal-app-client-id>" # Asgardeo sets the client ID as the aud claim +callback_url = "https://<your-domain>/api-portal/<org-handle>/callback" +logout_url = "https://api.asgardeo.io/t/<your-tenant>/oidc/logout" +logout_redirect_uri = "https://<your-domain>/api-portal/<org-handle>" +scope = "openid profile email roles" + +# Which token claim carries each field. Asgardeo B2B puts the sub-org handle in org_name. +[api_portal.auth.claim_mappings] +organization = "org_name" +roles = "roles" +groups = "groups" + +# Expands the roles claim into dp:* scopes, and gates pages on the same two role +# names. This section is mode-independent — it is NOT under [api_portal.auth.idp]. +[api_portal.auth.authorization] +enabled = true +mode = "role" +role_to_scope_mapping = "/etc/api-portal/role-to-scope-mapping.yaml" +page_role_validation = true + +[api_portal.auth.authorization.portal_roles] +admin = "dp_admin" +subscriber = "dp_subscriber" +``` + +{% endraw %} + +`mode = "idp"` selects the identity provider backend and stops the local login form from being used. `callback_url` must exactly match one of the authorized redirect URLs you registered in step 2. A single `callback_url` is shared across all portal organizations—after the callback, the portal uses the session's stored return path to redirect the user to the correct organization, so you register only this one URL with Asgardeo. + +Replace `<org-handle>` in `callback_url` and `logout_redirect_uri` with the `[api_portal.organization] handle` from step 5, and keep it identical to the redirect URLs registered in step 2. + +Never write the client secret as a literal in `config.toml`—the {% raw %}`{{ env }}`{% endraw %} placeholder above reads it from an environment variable instead, so it never has to be committed to source control: + +```bash +export APIP_AP_AUTH_IDP_CLIENT_SECRET=<api-portal-app-client-secret> +``` + +In a production deployment, prefer supplying it from a mounted secret file instead, by swapping the token for {% raw %}`'{{ file "/secrets/api-portal/oidc_client_secret" }}'`{% endraw %} and mounting the secret at that path—resolution fails closed, so a missing or unreadable file aborts startup rather than falling back to an empty credential. + +## Step 5: Align the portal handle with the sub-organization + +Asgardeo puts the sub-organization's handle in the `org_name` claim, and the portal resolves that claim against the organization it serves. The two names have to agree, so set `[api_portal.organization] handle` to the Asgardeo sub-org's **handle**—the URL slug shown in the Asgardeo console: + +```toml +[api_portal.organization] +handle = "acme" # the Asgardeo sub-org handle +display_name = "Acme" +``` + +Set this before the portal first starts. The handle is what the portal writes into the organization's **IDP reference ID** when it seeds the organization row, and that field is fixed afterward—the Management API rejects a request that changes it. Changing the handle later means seeding a new organization. + +The handle is also the URL slug in `/api-portal/{handle}/views/{viewName}`, so it appears in every portal URL. The portal normalizes it to lowercase, though the claim match itself tolerates any case. + +With the two aligned, the login flow closes: + +1. A user selects **Login**, and the portal appends `org=<handle>` to the Asgardeo authorization URL. +2. Asgardeo scopes the login session to that sub-organization, and issues a token whose `org_name` claim identifies it. +3. On every authenticated request, the portal resolves that claim against the organization it serves. A token minted for a different sub-organization resolves elsewhere and is refused with `403`. + +Two consequences worth knowing: + +- Public pages (the API catalog and documentation) stay accessible without authentication. +- Protected pages (applications, subscriptions, API keys) need a token whose `org_name` matches, so a user reaching a different portal organization has to log out and log in again there. + +!!! note "If the handle can't match" + When the sub-org handle isn't a name you want in your portal URLs, the alternative is to have Asgardeo emit a separate claim carrying the portal handle as a constant, and map `organization` to that claim instead. Doing so drops the sub-organization distinction from the check—every sub-org's token then carries the same value—so choose it only for a tenant with a single sub-organization. See [when your IdP has no organization concept](connect-an-identity-provider.md#when-your-idp-has-no-organization-concept). + +## Step 6: Restart and verify + +Restart the portal so it reloads the configuration: + +```bash +docker compose up -d --force-recreate +``` + +Open the portal and select **Login**. Instead of the built-in username and password form, you're redirected to the Asgardeo-hosted login page, and land back on the page you started from after signing in. Signing in as a `dp_admin` user also reveals the **Settings** area. + +## Claim flow summary + +The Asgardeo token carries these claims through to the API Portal & MCP Hub: + +| Claim | Purpose | Configured as | +|-------|---------|----------------| +| `sub` | User identity | Read under a fixed name, not configurable | +| `org_name` | Sub-organization handle, resolved against the portal's `[api_portal.organization] handle` | `organization` in `[api_portal.auth.claim_mappings]` | +| `roles` | Role list. Expanded into Management API scopes through `role_to_scope_mapping`, and matched against `[api_portal.auth.authorization.portal_roles]` for page access | `roles` in `[api_portal.auth.claim_mappings]` | + +Keep the claim names consistent between the Asgardeo token attributes and the `[api_portal.auth.claim_mappings]` table. + +!!! important "Two retired keys abort startup" + Earlier versions configured roles under `[api_portal.auth.idp.roles]`, with a third `super_admin` tier. That section is retired, along with `auth.role_validation`, and leaving either in `config.toml` fails startup rather than silently applying a default. Use `[api_portal.auth.authorization.portal_roles]` and `auth.authorization.page_role_validation` instead—see [Authorization](../../references/configurations.md#authorization). + +## Alternative: let Asgardeo mint the `dp:*` scopes + +`mode = "role"` above needs no scope registration in Asgardeo, which is why this tutorial uses it. If you'd rather have Asgardeo issue the portal's `dp:*` scopes directly and set `mode = "scope"`, register them in your tenant first. A helper script does the registration through Asgardeo's own management APIs. + +1. Create a new OIDC application in Asgardeo, for example named `DevPortal System`. +2. Under **API Authorization**, add the **API Resource Management API** and the **Application Management API**. +3. Note its client ID and client secret. +4. Download the script and run it: + + ```bash + curl -sLO https://raw.githubusercontent.com/wso2/api-platform/main/portals/api-portal/production/scripts/register_asgardeo_scopes.sh + chmod +x register_asgardeo_scopes.sh + + ASGARDEO_TENANT=<your-tenant> \ + ASGARDEO_CLIENT_ID=<system-app-client-id> \ + ASGARDEO_CLIENT_SECRET=<system-app-client-secret> \ + ASGARDEO_RESOURCE_IDENTIFIER=https://<your-domain> \ + ./register_asgardeo_scopes.sh + ``` + +5. Open the **API Portal & MCP Hub** application, and under **API Authorization** add the API resource the script created. +6. Assign the `dp:*` scopes to your roles, giving administrators the full set and subscribers only what everyday consumer operations need—`dp:application:manage`, `dp:subscription:manage`, `dp:api_key:manage`, and the `dp:*:read` scopes for browsing the catalog. +7. Set `mode = "scope"` in `[api_portal.auth.authorization]`. + +The script registers an API resource representing the portal, with all `dp:*` scopes under it. For local testing, its default `ASGARDEO_RESOURCE_IDENTIFIER=https://localhost:9543` works unchanged. The system application is only needed to run the script, and can be deleted afterward. + +In scope mode, browser sessions are preauthorized. For a user signed in through Asgardeo, the portal skips the per-operation scope check. Page role gating is the authorization that applies to them instead. The `dp:*` scopes then govern machine clients that call `/api-portal/api/v0.9` with a Bearer token. + +## Related topics + +- [Connect an identity provider to the API Portal](connect-an-identity-provider.md): the general procedure this tutorial is one example of +- [Authentication in the API Portal & MCP Hub](overview.md): how identity provider authentication compares with local authentication +- [Get a bearer token via curl](../../references/get-a-bearer-token-via-curl.md): calling the Management API with a token from your IdP +- [Configurations](../../references/configurations.md): every `config.toml` key diff --git a/en/docs/api-portal/next/setting-up/authentication/connect-an-identity-provider.md b/en/docs/api-portal/next/setting-up/authentication/connect-an-identity-provider.md new file mode 100644 index 000000000..bd9aa96c2 --- /dev/null +++ b/en/docs/api-portal/next/setting-up/authentication/connect-an-identity-provider.md @@ -0,0 +1,269 @@ +--- +title: "Connect an identity provider to the API Portal" +description: "Configure the API Portal & MCP Hub to delegate login to any OIDC identity provider: client registration, claim mappings, role or scope authorization, and the config.toml tables involved." +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/authentication/connect-an-identity-provider/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/authentication/connect-an-identity-provider.md +tags: + - cloud + - api-portal + - authentication + - oidc +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-04 +content_type: "how-to" +--- + +# Connect an identity provider to the API Portal + +The API Portal & MCP Hub delegates user login to any identity provider (IdP) that speaks OpenID Connect (OIDC). This guide is written for the administrator who deploys the portal, and it covers the configuration every IdP needs. For a worked example of these steps against one specific IdP, see [Set up Asgardeo as your identity provider](asgardeo-as-idp.md). + +For how identity provider authentication compares with local authentication, see [Authentication in the API Portal & MCP Hub](overview.md). + +## How OIDC login works here + +The portal itself is the OIDC client, not the browser. It runs the authorization code exchange server-side with PKCE and a state parameter, keeps the resulting tokens in a server-side session, and gives the browser only a session cookie. Register the portal as a **confidential** client, not a public or single-page application client. + +Two points shape the rest of this guide: + +- **There is no OIDC discovery.** The portal never reads `/.well-known/openid-configuration`, so you configure each endpoint URL explicitly in `[api_portal.auth.idp]`. +- **Two token types are read.** Identity claims (organization, roles, groups, name, email) come from the **ID token**. The `scope` claim, used only in `mode = "scope"`, comes from the **access token**. + +Clicking **Login** in `mode = "idp"` redirects straight to the IdP's authorization endpoint—the built-in username and password form is never shown, and `POST` to the local login route returns `404`. + +## What your identity provider must support + +Check your IdP against these requirements before you start: + +| Requirement | Details | +|-------------|---------| +| OIDC endpoints | Exposes authorization, token, userinfo, JWKS, and end-session endpoints. You supply each URL individually. | +| Confidential client | Issues a client secret, and accepts PKCE on the authorization code exchange. | +| Authorization code and refresh token grants | Both enabled on the application. The portal refreshes an expired access token before failing a Management API request. | +| JSON Web Token (JWT) access tokens | Access tokens are signed JWTs. The portal verifies a Bearer token's signature against the JWKS endpoint, so opaque tokens don't work for machine clients calling `/api-portal/api/v0.9`. | +| Custom claims | Emits the organization identifier and the user's roles in the ID token. Claim names are configurable; the claims themselves are required. | + +## Step 1: Register the portal as a confidential client + +In your IdP, create a confidential OIDC application. Replace `<your-domain>` with the address users reach the portal at, including the port when it isn't 443, and `<org-handle>` with the value of `[api_portal.organization] handle`—`default` in the packaged configuration. + +1. Set the authorized redirect URL to `https://<your-domain>/api-portal/<org-handle>/callback`. +2. Set the post-logout redirect URL to `https://<your-domain>/api-portal/<org-handle>`. Most IdPs validate `post_logout_redirect_uri` against the same list as the login callback, so add both URLs there. +3. Enable the **Authorization Code** and **Refresh Token** grants. +4. Set the access token type to **JWT**. +5. Configure the IdP to emit `given_name`, `family_name`, `email`, `roles`, and the claim carrying the organization identifier **in the ID token**. The portal reads user and organization identity from the ID token, not the access token, so a claim released only on the access token or the userinfo endpoint won't be seen. +6. Record the client ID and client secret. + +One redirect URL covers the whole portal. After the callback, the portal routes the user from the return path stored in their session, so there are no per-view or per-page redirect URLs to register. + +## Step 2: Map the claims the portal reads + +The portal reads three claims out of the ID token by name, and `[api_portal.auth.claim_mappings]` says which name carries each one. Either configure your IdP to emit the default names, or keep your IdP's names and set the mappings to match. + +| Mapping key | Default claim | Carries | +|-------------|---------------|---------| +| `organization` | `org_name` | The organization identifier, resolved against the organization this instance serves on every request. Required—a token without it is rejected with `403`. See [step 5](#step-5-make-the-organization-claim-resolve-to-your-organization). | +| `roles` | `roles` | The user's role names. Required when `authorization.mode = "role"`; startup fails if the mapping is empty. | +| `groups` | `groups` | The user's group names. Carried into the session for use in page and content rules. | + +Each value is either a flat top-level claim name or a dot-separated path into a nested claim. That path syntax accommodates providers that nest their claims—Keycloak puts roles under `realm_access.roles`, for example. + +A roles or groups claim may arrive as a JSON array or as a space- or comma-separated string. The portal accepts both. + +The portal also reads these fixed claims, whose names aren't configurable: `sub` for the user identity, `given_name` (falling back to `nickname`) and `family_name` for the display name, `email` for the address shown in the profile menu, and `picture` for the avatar. + +## Step 3: Choose how privileges reach the token + +The portal's Management API (`/api-portal/api/v0.9`) guards each operation with a `dp:*` scope. `[api_portal.auth.authorization] mode` decides where a request's effective scopes come from. Pick the one that matches what your IdP can put in a token. + +**Role mode** (`mode = "role"`, the default) expands the token's roles claim through a YAML grant table and ignores the token's own scope claim entirely. Your IdP only has to emit role names, which most products do out of the box, and a caller can't widen a role's grant by requesting extra scopes. Set `role_to_scope_mapping` to the path of the table; the image ships an editable copy, which `docker-compose.yaml` mounts at `/etc/api-portal/role-to-scope-mapping.yaml`. It defines two roles: + +| Role | Grants | +|------|--------| +| `dp_admin` | Every action on the organization: content and theme, the API and MCP server catalog, views, labels, subscription plans, key managers, webhook subscribers, and every application and subscription | +| `dp_subscriber` | The consumer persona: own applications, subscriptions, and keys, plus read access to the catalog | + +The table also aliases `ap_admin` and `ap_subscriber` onto those same grants, because those are the role names the Platform API mints. Map your IdP's groups onto any of the four names, or add an entry of your own for a narrower grant. The portal validates every scope in the table against its OpenAPI specification at startup, so an undeclared `dp:*` scope fails startup rather than surfacing later as a role that logs in and is denied every request. + +When a token carries several roles, the effective scopes are the union of every matching entry—most permissive wins. A role name the table doesn't list contributes nothing, so the failure mode of a mistyped or unmapped role is a denied request, never an unintended grant. + +**Scope mode** (`mode = "scope"`) reads the access token's own `scope` claim. Use it when the IdP mints `dp:*` scopes directly, which means registering all of them in the IdP and granting them to the application. Browser sessions are preauthorized in this mode—the per-operation check is skipped, and page role gating is the authorization that applies to them. + +Role mode is the lighter integration of the two. Editing the mapping file needs a restart, since the portal reads it at startup. + +### Page access tiers + +Page access is separate from Management API scopes. `[api_portal.auth.authorization.portal_roles]` names the role that grants each of the portal's two page tiers, and `page_role_validation` switches the gate on: + +```toml +[api_portal.auth.authorization] +page_role_validation = true + +[api_portal.auth.authorization.portal_roles] +admin = "ap_admin" +subscriber = "ap_subscriber" +``` + +Point these at your IdP's role names, or at names in the grant table so one set of roles drives both page access and Management API authorization. + +## Step 4: Configure the portal + +Set the `[api_portal.auth]` tables in `configs/config.toml`. `mode` selects exactly one authentication backend, so `"idp"` also stops the local login form from being used: + +{% raw %} + +```toml +[api_portal.auth] +mode = "idp" + +[api_portal.auth.idp] +name = "my-idp" # friendly name, used in logs +issuer = "https://idp.example.com" # exact "iss" claim value, not an endpoint +authorization_url = "https://idp.example.com/oauth2/authorize" +token_url = "https://idp.example.com/oauth2/token" +user_info_url = "https://idp.example.com/oauth2/userinfo" +jwks_url = "https://idp.example.com/oauth2/jwks" +client_id = "<portal-client-id>" +client_secret = '{{ env "APIP_AP_AUTH_IDP_CLIENT_SECRET" }}' +audience = "<portal-client-id>" # expected "aud" claim +callback_url = "https://<your-domain>/api-portal/<org-handle>/callback" +logout_url = "https://idp.example.com/oidc/logout" +logout_redirect_uri = "https://<your-domain>/api-portal/<org-handle>" +scope = "openid profile email roles" + +# Which token claim carries each field. A sibling of [api_portal.auth.idp], not +# nested in it — dot notation reaches a nested claim, e.g. "realm_access.roles". +[api_portal.auth.claim_mappings] +organization = "org_name" +roles = "roles" +groups = "groups" + +# Applies in both auth modes, which is why it is NOT under [api_portal.auth.idp]. +[api_portal.auth.authorization] +enabled = true +mode = "role" +role_to_scope_mapping = "/etc/api-portal/role-to-scope-mapping.yaml" +page_role_validation = true + +[api_portal.auth.authorization.portal_roles] +admin = "ap_admin" +subscriber = "ap_subscriber" +``` + +{% endraw %} + +Four things to get right: + +- **`callback_url`** must match the URL registered in step 1 exactly, character for character. +- **`issuer`** is the IdP's issuer identifier—the exact string it puts in the token's `iss` claim—and is compared verbatim. It's a distinct value from `token_url`, though some providers do use their token endpoint URL as the issuer: Asgardeo and WSO2 Identity Server both issue `iss` as `.../oauth2/token`. Decode a token from your IdP and copy `iss` out of it rather than assuming either form. +- **`audience`** must match the token's `aud` claim. Set it to your client ID rather than leaving it empty, so a token minted for a different application is rejected. +- **`scope`** must request whatever the IdP needs to emit the organization and roles claims. Keep `openid`, and add the scope your IdP attaches role information to. + +!!! important "Two retired keys abort startup" + Earlier versions configured roles under `[api_portal.auth.idp.roles]`, with a third `super_admin` tier. That section is retired, along with `auth.role_validation`, and leaving either in `config.toml` fails startup rather than silently applying a default. Use `[api_portal.auth.authorization.portal_roles]` and `auth.authorization.page_role_validation` instead—see [Authorization](../../references/configurations.md#authorization). + +### Supply the client secret + +Never write the client secret as a literal in `config.toml`. The {% raw %}`{{ env }}`{% endraw %} token above reads it from an environment variable instead, so it never has to be committed to source control: + +```bash +export APIP_AP_AUTH_IDP_CLIENT_SECRET=<portal-client-secret> +``` + +In production, prefer a mounted secret file. Swap the token for {% raw %}`'{{ file "/secrets/api-portal/oidc_client_secret" }}'`{% endraw %}, then mount the secret at that path. Both forms fail closed: a missing variable or unreadable file aborts startup rather than falling back to an empty credential. See [Interpolation tokens](../../references/configurations.md#interpolation-tokens). + +## Step 5: Make the organization claim resolve to your organization + +A portal instance serves exactly one organization, and the database schema is multi-organization—so a token your IdP correctly signed for a *different* organization would pass every signature, expiry, and audience check. The portal closes that gap by resolving the mapped organization claim and confirming it names the organization the instance is pinned to. It does this at login and on every authenticated request, and a mismatch is a flat `403`, whether the asserted organization is unknown or merely someone else's. A token carrying no organization claim at all is rejected the same way. + +The claim's value has to resolve to your organization, which means it must equal either of these: + +| Value | Matching | +|-------|----------| +| The organization handle, from `[api_portal.organization] handle` | Case-insensitive | +| The organization display name, from **Settings > Organization** | Exact | + +The handle is also the organization's **IDP reference ID**, which the portal writes when it first seeds the organization row. That field is fixed from then on: the Management API rejects a request that changes it, so **IDP reference ID** in [Organization settings](../../admin-settings/organization-settings.md) reflects the handle rather than offering a second value to match against. + +So point the claim at the handle. Which way round you do that depends on what your IdP has to assert. + +### When your IdP asserts an organization identifier + +Providers with a business-to-business organization model—Asgardeo sub-organizations, an Entra ID tenant—already put an organization identifier in the token. Set `[api_portal.organization] handle` to that identifier, and map the claim carrying it: + +```toml +[api_portal.organization] +handle = "acme" # the identifier the IdP asserts + +[api_portal.auth.claim_mappings] +organization = "org_name" +``` + +This is the option to prefer when it's available to you. The claim keeps carrying a real organization identity, so a token minted for a different organization still resolves elsewhere and is still refused. It also keeps the authorization request correct: the portal appends `org=<handle>` to it, and a provider with a sub-organization model reads that parameter to scope the login session to the matching sub-organization. + +The handle is the URL slug in `/api-portal/{handle}/views/{viewName}`, so it becomes part of every portal URL. Choose it with that in mind—and note that the portal normalizes it to lowercase, though claim matching tolerates any case. + +### When your IdP has no organization concept + +A single Keycloak realm, an Auth0 tenant, or an Okta org has one user population and nothing organization-shaped to assert. Add a custom claim to the token whose value is the portal's organization handle, and map that claim: + +```toml +[api_portal.organization] +handle = "default" + +[api_portal.auth.claim_mappings] +organization = "org_id" # your custom claim, emitted with the constant value "default" +``` + +Configure the claim in the IdP as a constant—Keycloak calls this a hardcoded claim mapper, Auth0 an action that adds a custom claim. A fixed value is the accurate thing to assert here: the IdP serves one user population, the portal serves one organization, and the claim says which organization a token is for. + +!!! important "A constant claim asserts nothing about isolation" + Every token the IdP issues then carries the value the portal expects, so the organization check passes for every user the IdP will authenticate. That's the intended outcome for a single-population IdP. It is not what you want from a provider serving several organizations through one shared application—there, set the handle to the asserted identifier instead, as described above, and let the check do its job. + +## Step 6: Restart and verify + +Restart the portal so it reloads the configuration: + +```bash +docker compose up -d --force-recreate +``` + +Open the portal and select **Login**. Instead of the username and password form, you're redirected to your IdP's hosted login page, and land back on the page you started from after signing in. + +If sign-in fails, the mismatch is usually one of these: + +- `callback_url` differs from the redirect URL registered in the IdP. +- `issuer` doesn't match the token's `iss` claim, or `audience` doesn't match its `aud` claim. +- The ID token doesn't carry the claim names configured in `[api_portal.auth.claim_mappings]`, so the organization or roles claim is never found. +- The organization claim's value is neither the organization handle nor its display name, so it resolves to no organization the instance serves. +- The IdP issues opaque access tokens rather than JWTs, which fails Bearer-token requests to `/api-portal/api/v0.9` while browser login still works. + +Set `[api_portal.logging] level = "debug"` to see which claim or check fails. + +## Optional settings + +These `[api_portal.auth.idp]` keys tune the login experience. Leave them at their defaults unless you need the behavior: + +| Key | Default | Effect | +|-----|---------|--------| +| `silent_sso` | `true` | Attempts a `prompt=none` authorization on the first page load, so a user with a live IdP session arrives already signed in. Set to `false` to require an explicit **Login**. | +| `org_callback` | `false` | Sends a user with no stored return path to the organization's landing page after login, rather than the portal root. | +| `sign_up_url` | empty | The IdP's self-registration page. The portal's **Sign up** route redirects here; without it the route has nowhere to send the user. | +| `token_refresh_timeout_ms` | `10000` | How long the portal waits on the IdP's token endpoint when refreshing an expired access token. | +| `certificate` | empty | An X.509 certificate used to verify Bearer-token signatures instead of fetching the JWKS endpoint. Set it only for an IdP whose JWKS endpoint the portal can't reach; browser login always uses `jwks_url`. | + +## Claim names for common identity providers + +The roles claim is the one that most often differs. These paths are known to work: + +| Identity provider | `claim_mappings` roles value | +|-------------------|------------------------------| +| Asgardeo | `roles` | +| Microsoft Entra ID | `roles` | +| Keycloak | `realm_access.roles`, or `resource_access.<client>.roles` | + +## Related topics + +- [Set up Asgardeo as your identity provider](asgardeo-as-idp.md): these steps apply to Asgardeo, including `dp:*` scope registration +- [Authentication in the API Portal & MCP Hub](overview.md): how identity provider authentication compares with local authentication +- [Configurations](../../references/configurations.md): every `config.toml` key, and how interpolation tokens deliver values into it +- [Organization settings](../../admin-settings/organization-settings.md): the organization's display name and references, as administrators see them diff --git a/en/docs/api-portal/next/setting-up/authentication/overview.md b/en/docs/api-portal/next/setting-up/authentication/overview.md new file mode 100644 index 000000000..91ff03a7f --- /dev/null +++ b/en/docs/api-portal/next/setting-up/authentication/overview.md @@ -0,0 +1,97 @@ +--- +title: "Authentication in the API Portal & MCP Hub" +description: "Understand the two ways users sign in to the API Portal & MCP Hub: local authentication against the Platform API for development, and an OIDC identity provider for production." +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/authentication/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/authentication/overview.md +tags: + - cloud + - api-portal + - authentication +author: WSO2 API Platform Documentation Team +last_updated: 2026-07-24 +content_type: "concept" +--- + +# Authentication in the API Portal & MCP Hub + +The API Portal & MCP Hub reads its settings from a single `config.toml` file, under the `[api_portal.*]` tables. Authentication is controlled by `mode` in the `[api_portal.auth]` table, which selects between two backends. A running instance uses one mode at a time. + +| Mode | `[api_portal.auth] mode` | Best for | +|------|-------------------------------|----------| +| Local | `local` | Development and local testing, no identity provider required | +| Identity provider | `idp` | Production, where a dedicated OIDC identity provider manages user login | + +The block matching your chosen mode is used; the other is ignored. + +## Login flow + +The two modes present users with different login experiences: + +- **Local mode** (`mode = "local"`): clicking **Login** on any portal page shows a built-in username and password form. Credentials are validated against the Platform API. + +- **Identity provider mode** (`mode = "idp"`): clicking **Login** redirects the user directly to the identity provider's authorization endpoint—no intermediate login page is shown. After authenticating, the user is returned to the page they originally requested. + +Public pages (the API catalog and documentation) are always accessible without authentication in either mode. Only protected pages—applications, subscriptions, and API keys—require login. + +## Local authentication + +Local authentication delegates credential validation to the Platform API control plane. It requires no external identity provider, which makes it the default for local development and quick trials. + +When `[api_portal.auth] mode = "local"`, the portal renders a username and password form and validates the credentials against the Platform API. Users, bcrypt-hashed passwords, and roles are defined in the Platform API's own configuration, under `[[platform_api.auth.file.users]]`: + +```toml +[[platform_api.auth.file.users]] +username = "admin" +password_hash = "..." +roles = ["ap_admin"] +``` + +Those role names are what the portal authorizes against. The Platform API mints them into the `roles` claim of the token it issues, and because `authorization.mode = "role"` is the default, the portal expands that claim through its grant table—which aliases `ap_admin` and `ap_subscriber` onto its own `dp_admin` and `dp_subscriber` grants for exactly this reason. The shipped setup therefore works unchanged. See [Choose how privileges reach the token](connect-an-identity-provider.md#step-3-choose-how-privileges-reach-the-token) for what each role grants. + +```toml +[api_portal.auth] +mode = "local" + +[api_portal.auth.local] +# The upstream Platform API used to validate credentials. +platform_api_url = "https://platform-api:9243" +# Path to the Platform API's RS256 public key PEM — the matching half of its +# [platform_api.auth.jwt] private key. Bearer-token requests fail closed without it. +public_key_path = "/etc/api-portal/keys/jwt_public.pem" +tls_skip_verify = false +``` + +Leave `platform_api_url` empty to disable local authentication entirely. + +Local authentication is intended for development and local testing only. Move to an identity provider before deploying to a shared or production environment. + +## Identity provider authentication + +For production, configure the portal to delegate login to an identity provider (IdP) over OpenID Connect (OIDC). The API Portal & MCP Hub works with any OIDC-compliant IdP—such as Asgardeo, Keycloak, Auth0, or Okta—that meets these requirements: + +| Requirement | Details | +|-------------|---------| +| OIDC endpoints | The IdP exposes authorization, token, userinfo, and end-session endpoints. You configure each URL individually; the portal doesn't read `/.well-known/openid-configuration` | +| JSON Web Token (JWT) access tokens | Access tokens are JWTs, not opaque tokens | +| Signature verification | The IdP exposes a JSON Web Key Set (JWKS) endpoint, so the portal can verify the signature on a Bearer token | +| Confidential client | The portal is registered as a confidential client with a client secret (a server-side Traditional Web Application), not a public single-page application | +| Claims | Tokens carry the organization identifier and the user's roles as claims (claim names are configurable) | + +When `mode = "idp"`, the portal reads the `[api_portal.auth.idp]` block for the OIDC endpoints and client credentials, and the `[api_portal.auth.claim_mappings]` block for the claim names that carry organization and role information. + +[Connect an identity provider to the API Portal](connect-an-identity-provider.md) covers the configuration every IdP needs. For a worked example against one specific provider, see [Set up Asgardeo as your identity provider](asgardeo-as-idp.md). + +## Authorization is configured separately + +Authentication decides who a caller is. Authorization decides what they may do. The two are configured in different places. + +`[api_portal.auth.authorization]` applies in **both** modes. The portal verifies a token the same way whether it came from an IdP's JWKS endpoint or the Platform API's public key. + +That section holds the role-to-scope mapping, the switch for Management API scope enforcement, per-page role gating, and the role names granting the admin and subscriber tiers. See [Authorization](../../references/configurations.md#authorization). + +!!! important + Two keys that used to live here are retired, and leaving either in `config.toml` aborts startup: `auth.role_validation` is now `auth.authorization.page_role_validation`, and `auth.idp.roles` is now `auth.authorization.portal_roles`. + +## Choosing a mode + +Use local authentication when you're trying out the API Portal & MCP Hub, running a demo, or don't yet have an identity provider available. Move to an identity provider before you deploy to a shared or production environment, need to serve multiple organizations, or want single sign-on with an existing identity system. diff --git a/en/docs/api-portal/next/setting-up/configuration.md b/en/docs/api-portal/next/setting-up/configuration.md new file mode 100644 index 000000000..ecb3a0667 --- /dev/null +++ b/en/docs/api-portal/next/setting-up/configuration.md @@ -0,0 +1,113 @@ +--- +title: "API Portal configuration and environment interpolation" +description: "How the API Portal & MCP Hub loads its config.toml, injects environment values and mounted files through interpolation tokens, and keeps sensitive values out of the config file." +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/configuration/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/configuration.md +tags: + - cloud + - api-portal + - configuration + - interpolation +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "reference" +--- + +# API Portal configuration and environment interpolation + +The API Portal & MCP Hub reads its configuration from a TOML file (`config.toml`) layered over built-in defaults. The quickstart runs it alongside a Platform API, which reads the same file, so both are covered here — a standalone production deployment needs only the portal's own section. + +This page explains how each service loads its config file. It also covers how environment values and mounted files are injected through interpolation tokens, and how to keep sensitive values out of the file. For the full reference of every supported key, see [Configurations](../references/configurations.md). For provisioning the keys, certificates, and credentials those tokens resolve to, see [Getting started](../getting-started.md). + +## How configuration is loaded + +Each service reads a TOML file mounted into its container, layered over that service's built-in defaults: + +- **API Portal & MCP Hub** — `/app/configs/config.toml`; every key lives under the `[api_portal]` table. Defaults come from `src/config/configDefaults.js`. +- **Platform API** — `/etc/platform-api/config.toml`; every key lives under the `[platform_api]` table. + +Precedence runs lowest to highest: built-in defaults, then `configs/config.toml` with its interpolation tokens resolved. + +The per-service namespacing (`[api_portal]`, `[platform_api]`, `[ai_workspace]`) lets one `config.toml` hold multiple services' sections side by side without their keys colliding — each service reads only its own table. The shipped stack relies on this: the API Portal and AI Workspace containers mount the same file and each ignore the other's section. + +`configs/config-template.toml` documents every supported key with its default as a plain literal. It's a reference copy, not the file the portal reads. + +!!! important "Environment variables don't override config keys directly" + There is **no prefix that auto-maps environment variables onto config keys.** An environment value reaches a setting **only** through an explicit interpolation token written into the config file, resolved when the file is loaded. A key written as a plain literal — or absent from the file — ignores the matching variable entirely. + +## Interpolation tokens + +Two functions are available inside `config.toml`: + +{% raw %} + +| Token | Behavior | +|-------|----------| +| `{{ env "NAME" }}` | Substitutes the value of environment variable `NAME`. **Fails closed** — an unset or empty variable aborts startup rather than falling 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 — for injecting secrets from a mounted volume rather than an environment variable. Always required: a missing, unreadable, oversized, or disallowed path is a hard startup error. | + +An example from the shipped API Portal `config.toml`: + +```toml +[api_portal.server] +port = '{{ env "APIP_AP_SERVER_PORT" "9543" }}' + +[api_portal.security] +encryption_key = '{{ file "/etc/api-portal/keys/encryption.key" }}' +``` + +{% endraw %} + +Partial substitution works too — {% raw %}`'foo-{{ env "X" }}'`{% endraw %} resolves to `foo-bar` when `X=bar`. + +A templated value is coerced to its natural type after substitution, so `"true"` becomes a boolean and `"9543"` becomes a number. A plain TOML literal keeps its native type and is never passed through coercion. + +!!! note "The variable name is a naming convention, not a prefix override" + By convention each token names the key's dotted path, uppercased with dots as underscores, behind a per-service prefix. The prefixes are `APIP_AP_` for the API Portal, `APIP_CP_` for the Platform API, and `APIP_AIW_` for AI Workspace. For example, `[api_portal.server] port` becomes `APIP_AP_SERVER_PORT`. The loader doesn't interpret the prefix — the name is only the literal string you pass to the interpolation function. You can rename any variable, as long as you edit the matching token in `config.toml` to agree. + +### Which variables your deployment reads + +Because the variable names live in `config.toml`, the config file is the authoritative list for your deployment — not this page. To see which settings your stack injects from the environment, search the mounted `config.toml` for {% raw %}`{{ env`{% endraw %} and read the name out of each token. + +For every configurable option and the tokens the shipped files carry, refer to the config templates: [API Portal](https://github.com/wso2/api-platform/blob/main/portals/api-portal/configs/config-template.toml) and [Platform API](https://github.com/wso2/api-platform/blob/main/platform-api/config/config-template.toml). + +## Sensitive values in `config.toml` + +This section covers credentials the services need to start — the at-rest encryption key, the session secret, the OpenID Connect (OIDC) client secret, and any database password. It's a separate mechanism from the credentials the portal stores for applications and API keys, which live encrypted in its database. + +Never write a sensitive value as a literal in `config.toml`, and never hardcode one in `docker-compose.yaml`. Reference each with an interpolation token — from a mounted file, or from an environment variable: + +{% raw %} + +```toml +# The shipped config.toml reads both portal secrets from mounted files: +[api_portal.security] +encryption_key = '{{ file "/etc/api-portal/keys/encryption.key" }}' +session_secret = '{{ file "/etc/api-portal/keys/session-secret" }}' + +# OIDC client secret (idp mode) - env var, or preferably a mounted file: +[api_portal.auth.idp] +client_secret = '{{ env "APIP_AP_AUTH_IDP_CLIENT_SECRET" }}' +# client_secret = '{{ file "/secrets/api-portal/oidc_client_secret" }}' +``` + +{% endraw %} + +None of these carries a default, so each is a required secret. Both forms fail closed: if the variable is unset or empty, or the file is missing or outside the allowed source directories, the service refuses to start rather than run with an empty credential. + +A {% raw %}`{{ file }}`{% endraw %} path must live under an allowed directory — `/etc/api-portal` or `/secrets/api-portal` for the portal, `/etc/platform-api` or `/secrets/platform-api` for the Platform API. Override the list with the shared `APIP_CONFIG_FILE_SOURCE_ALLOWLIST` (comma-separated; it **replaces** the defaults rather than extending them). That variable is read straight from the process environment rather than through {% raw %}`{{ env }}`{% endraw %}, since it gates interpolation itself. Files are also capped at 1 MiB, and traversal sequences and symlinks escaping the allowlist are rejected before the read. + +## Where the values come from + +Provisioning the keys, certificates, and credentials the tokens resolve to is part of setting the stack up, not part of how the config loader works. For those steps, see: + +- [Run the setup script](../getting-started.md#step-2-run-the-setup-script) — what a fresh stack is given, and where each artifact lands under `resources/certificates/` and `resources/keys/`. +- The script is idempotent: re-running it fills in only what's missing. To rotate a secret, remove it from `api-platform.env` or delete the relevant file, then re-run. +- `api-platform.env` is the file Compose loads into every container, so it's where an environment value a token names is set. + +## Related + +- [Configurations](../references/configurations.md): the full reference of every supported key +- [Getting started](../getting-started.md): provision the keys, certificates, and credentials the tokens resolve to +- [Change the ports the API Portal uses](ports.md): move the stack off its default ports diff --git a/en/docs/api-portal/next/setting-up/database.md b/en/docs/api-portal/next/setting-up/database.md new file mode 100644 index 000000000..eeedcba76 --- /dev/null +++ b/en/docs/api-portal/next/setting-up/database.md @@ -0,0 +1,191 @@ +--- +title: "Set up the database" +description: "Configure the API Portal & MCP Hub to run on SQLite, PostgreSQL, or Microsoft SQL Server: connection settings, schema, and Transport Layer Security (TLS)." +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/database/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/database.md +tags: + - cloud + - api-portal + - database + - setting-up +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-03 +content_type: "how-to" +--- + +{% raw %} + +# Set up the database + +The API Portal & MCP Hub stores its organization, catalog, application, subscription, and key data in a relational database. You can run it on any of three drivers: + +| Driver | `driver` value | Best for | +|---|---|---| +| SQLite | `sqlite` | Single-node deployments, evaluation, and development | +| PostgreSQL | `postgres` | Production and high-availability deployments | +| Microsoft SQL Server | `mssql` | Production deployments standardized on Microsoft SQL Server | + +You select the driver and its connection settings in the `[api_portal.database]` section of `config.toml`. For the full field reference, see [Configurations](../references/configurations.md). + +!!! note "Where the schema comes from" + SQLite applies its schema automatically at startup, so no manual step is needed. PostgreSQL and Microsoft SQL Server require you to apply the schema before the portal connects—see [Apply the schema for PostgreSQL or Microsoft SQL Server](#apply-the-schema-for-postgresql-or-microsoft-sql-server). + +## Choose a driver + +Set `driver` in `config.toml`, along with the connection fields the driver uses: + +```toml +[api_portal.database] +driver = "sqlite" # sqlite | postgres | mssql + +# SQLite only +path = "./api-portal.db" + +# PostgreSQL / MSSQL only +host = "localhost" +port = 5432 # 1433 for MSSQL +name = "api_portal" +user = "<db_user>" +password = '{{ env "APIP_AP_DATABASE_PASSWORD" }}' +``` + +Each field can also be supplied through an environment variable, which is useful for containerized deployments. The `config.toml` shipped with the portal reads these tokens: + +| Field | Environment variable | +|---|---| +| `driver` | `APIP_AP_DATABASE_DRIVER` | +| `path` | `APIP_AP_DATABASE_PATH` | +| `host` | `APIP_AP_DATABASE_HOST` | +| `port` | `APIP_AP_DATABASE_PORT` | +| `name` | `APIP_AP_DATABASE_NAME` | +| `user` | `APIP_AP_DATABASE_USER` | +| `password` | `APIP_AP_DATABASE_PASSWORD` | + +An environment variable takes effect only where `config.toml` references it with an `{{ env "..." }}` token. There's no automatic environment-variable override for arbitrary fields, so keep the tokens in place if you rely on them. + +!!! warning "Keep credentials out of source control" + Supply the database password through an environment variable or a secrets file, using an `{{ env "..." }}` or `{{ file "..." }}` token in `config.toml`. Don't write a plaintext password into `config.toml`, and don't commit credentials to version control. + +## SQLite + +SQLite is the default driver and needs no external database server. + +1. Set the driver and the database file path: + + ```toml + [api_portal.database] + driver = "sqlite" + path = "./data/api-portal.db" + ``` + +2. Make sure the directory that holds the file exists and is writable by the portal process. Under Docker Compose, the `/app/data` directory is created for you by the data volume mount. When you run the portal directly with `npm start`, create the target directory yourself first. +3. Start the portal. It applies the SQLite schema in-process on the first run, so the tables are ready without any further action. + +!!! note "Path is relative to the working directory" + A relative `path` resolves against the process working directory. Under Docker Compose that's `/app`, so `./data/api-portal.db` maps to `/app/data/api-portal.db`. + +## PostgreSQL + +1. Provision a PostgreSQL database. Create a dedicated application account for the portal, and reserve an administrative account (such as `postgres`) for applying the schema. +2. Apply the PostgreSQL schema (see [Apply the schema for PostgreSQL or Microsoft SQL Server](#apply-the-schema-for-postgresql-or-microsoft-sql-server)). +3. Point the portal at the database with the dedicated application account: + + ```toml + [api_portal.database] + driver = "postgres" + host = "localhost" + port = 5432 + name = "api_portal" + user = "<api_portal_db_user>" + password = '{{ env "APIP_AP_DATABASE_PASSWORD" }}' + ``` + +4. Configure the [connection pool](#connection-pool) and [TLS](#tls-for-postgresql-and-microsoft-sql-server) as needed, then start the portal. + +## Microsoft SQL Server + +1. Provision a Microsoft SQL Server database. Create a dedicated application login for the portal, and reserve an administrative login (such as `sa`) for applying the schema. +2. Apply the Microsoft SQL Server schema (see [Apply the schema for PostgreSQL or Microsoft SQL Server](#apply-the-schema-for-postgresql-or-microsoft-sql-server)). +3. Point the portal at the database with the dedicated application login. Microsoft SQL Server listens on port `1433` by default: + + ```toml + [api_portal.database] + driver = "mssql" + host = "localhost" + port = 1433 + name = "api_portal" + user = "<api_portal_db_user>" + password = '{{ env "APIP_AP_DATABASE_PASSWORD" }}' + ``` + +4. Configure the [connection pool](#connection-pool) and [TLS](#tls-for-postgresql-and-microsoft-sql-server) as needed, then start the portal. + +## Apply the schema for PostgreSQL or Microsoft SQL Server + +Unlike SQLite, the portal doesn't create tables for PostgreSQL or Microsoft SQL Server. Apply the matching schema script against an empty database before the portal connects, as a provisioning or continuous integration (CI) step. The scripts ship with the distribution under `resources/api-portal/db-scripts/`: + +| Driver | Schema script | +|---|---| +| PostgreSQL | `schema.postgres.sql` | +| Microsoft SQL Server | `schema.sqlserver.sql` | + +To apply the schema, follow these steps from the distribution root: + +1. Connect to the target database with an administrative account. +2. Run the schema script for your driver: + + - For PostgreSQL, use `psql`: + + ```bash + psql -h localhost -U <admin_user> -d api_portal -f resources/api-portal/db-scripts/schema.postgres.sql + ``` + + - For Microsoft SQL Server, use `sqlcmd`: + + ```bash + sqlcmd -S localhost,1433 -U <admin_user> -d api_portal -i resources/api-portal/db-scripts/schema.sqlserver.sql + ``` + +## Connection pool + +The `postgres` and `mssql` drivers use a connection pool. The defaults suit most deployments; tune them for your load: + +```toml +[api_portal.database] +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 +``` + +!!! warning "Pool settings are validated at startup" + The following constraints apply to the `postgres` and `mssql` drivers: + + - `max_open_conns` must be an integer of at least 1. + - The remaining pool settings must be non-negative integers. + - `min_open_conns` must not exceed `max_open_conns`. + + An invalid value stops startup with a `[FATAL]` message rather than reaching the connection pool. + +SQLite ignores these settings. + +## TLS for PostgreSQL and Microsoft SQL Server + +To encrypt the database connection with Transport Layer Security (TLS), set `ssl_mode`. The default is `disable`: + +```toml +[api_portal.database] +ssl_mode = "verify-full" # disable | verify-full +ssl_root_cert = "./resources/security/ca.pem" # certificate authority (CA) certificate, used by verify-full +``` + +With `verify-full`, the portal verifies the server certificate against the certificate authority (CA) certificate at `ssl_root_cert`. Provide a CA certificate the database server's certificate chains to. + +## Next steps + +- Set the required [security keys](../references/configurations.md#security) before starting the portal. +- Configure [authentication](authentication/overview.md). +- Return to the [Getting Started](../getting-started.md) guide to run the portal. + +{% endraw %} diff --git a/en/docs/api-portal/next/setting-up/ports.md b/en/docs/api-portal/next/setting-up/ports.md new file mode 100644 index 000000000..2fdec2ac1 --- /dev/null +++ b/en/docs/api-portal/next/setting-up/ports.md @@ -0,0 +1,165 @@ +--- +title: "Change the ports the API Portal uses" +description: "Move the API Portal & MCP Hub and the Platform API off their default ports, either through the APIP_AP_SERVER_PORT variable or by editing the Compose port mappings." +canonical_url: https://wso2.com/api-platform/docs/api-portal/setting-up/ports/ +md_url: https://wso2.com/api-platform/docs/api-portal/setting-up/ports.md +tags: + - cloud + - api-portal + - configuration +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Change the ports the API Portal uses + +The stack listens on these ports by default: + +| Port | Service | Purpose | +|------|---------|---------| +| `9543` | API Portal & MCP Hub | HTTPS — the browser entry point | +| `9243` | Platform API | HTTPS — the local-auth backend and control plane | +| `9643` | AI Workspace | HTTPS — only when you enable the `ai-workspace` profile | + +If another process on your machine holds one of these, or your organization reserves it, move the stack off it. The two services work differently: the portal's port is driven by a single environment variable, while the Platform API's is fixed in `docker-compose.yaml`. + +!!! note "Two stacks can't share a host" + Unpacking the distribution twice and starting both fails — each copy binds `9543` and `9243`. Change the ports on the second copy, or stop the first. + +## Change the API Portal port + +`docker-compose.yaml` reads the same variable on both sides of the portal's mapping and passes it into the container, and the shipped `configs/config.toml` binds the listener from it: + +{% raw %} + +```yaml + environment: + APIP_AP_SERVER_PORT: ${APIP_AP_SERVER_PORT:-9543} + ports: + - "${APIP_AP_SERVER_PORT:-9543}:${APIP_AP_SERVER_PORT:-9543}" +``` + +```toml +[api_portal.server] +port = '{{ env "APIP_AP_SERVER_PORT" "9543" }}' +``` + +{% endraw %} + +So one variable moves the published port and the listener together — no Compose edit required. + +1. Set the variable in `api-platform.env`, the file Compose loads into every service: + + ```bash + APIP_AP_SERVER_PORT=8443 + ``` + +2. Update `base_url` in `configs/config.toml`. **This does not follow the variable** — the shipped file writes it as a plain literal: + + ```toml + [api_portal.server] + base_url = "https://localhost:8443" + ``` + + Leaving it at `9543` doesn't stop the portal from serving. It only affects the absolute URLs embedded in generated AI-agent prompts, which would point at a port nothing listens on. To drive it from the environment instead, replace the literal with the token the config template ships: + + {% raw %} + + ```toml + base_url = '{{ env "APIP_AP_SERVER_BASE_URL" "https://localhost:9543" }}' + ``` + + {% endraw %} + +3. Recreate the containers, and open the portal on the new port: + + ```bash + docker compose up --force-recreate + ``` + + The portal is served under the `/api-portal` path prefix, so the full URL becomes `https://localhost:8443/api-portal/default/views/default`. + +## Change the Platform API port + +The Platform API's mapping is fixed at `9243:9243`, so this one is a Compose edit. Decide first whether you need the *published* port moved or the *listener* moved — the portal reaches the Platform API over the Compose network, not through the published port. + +### Remap the published port only + +Enough when the conflict is on your own machine. The container keeps listening on 9243, and Docker publishes it elsewhere: + +```yaml +services: + platform-api: + ports: + - "8244:9243" +``` + +Leave the container side and the `healthcheck` entry alone — both run inside the container, where 9243 still applies. Leave `platform_api_url` alone too, for the same reason (see [Two settings that aren't interchangeable](#two-settings-that-arent-interchangeable)). + +### Change the listener port + +Choose this when something inside the Docker network needs the new port. Add a `[platform_api.server.https]` table to `configs/config.toml` — the shipped file omits it, so include the certificate paths, which the Platform API requires on its HTTPS listener: + +```toml +[platform_api.server.https] +enabled = true +port = 8244 +cert_file = "/app/data/certs/cert.pem" +key_file = "/app/data/certs/key.pem" +``` + +Then update both sides of the mapping and the health check, which runs inside the container: + +```yaml +services: + platform-api: + ports: + - "8244:8244" + healthcheck: + test: ["CMD", "curl", "-fk", "https://localhost:8244/health"] +``` + +Finally point the portal at the new listener: + +```bash +APIP_AP_AUTH_LOCAL_PLATFORM_API_URL=https://platform-api:8244 +``` + +## Two settings that aren't interchangeable + +A port change touches values on either side of the Compose network boundary: + +| Setting | Who connects to it | Value | +|---------|--------------------|-------| +| `[api_portal.auth.local] platform_api_url` | The API Portal container, over the Compose network | A full URL using the internal service name and the **container** port — `https://platform-api:9243` | +| The published Platform API port | A browser, the `ap` CLI, or `curl` on your host | The **host** side of the Compose mapping | + +Setting `platform_api_url` to a published host port is the common mistake: the portal container can't resolve `localhost` to your machine, so every login fails while the Platform API itself looks healthy. Conversely, remapping only the published port and then editing `platform_api_url` breaks a portal that was working. + +## Ports in an OIDC setup + +OIDC redirect URLs carry the port, and both must sit under the portal's `/api-portal` mount. After a port change, update these in `configs/config.toml` and register the same values in your identity provider: + +```toml +[api_portal.auth.idp] +callback_url = "https://localhost:8443/api-portal/<org-handle>/callback" +logout_redirect_uri = "https://localhost:8443/api-portal/<org-handle>" +``` + +See [Connect an identity provider](authentication/connect-an-identity-provider.md). + +## Serving plain HTTP + +If a trusted upstream terminates TLS, turn the portal's own TLS off rather than changing ports — the single listener then serves plain HTTP on the same port: + +```bash +APIP_AP_SERVER_HTTPS_ENABLED=false +``` + +There is no self-signed fallback: with HTTPS enabled, `cert_file` and `key_file` must both resolve. + +## Related + +- [Configurations](../references/configurations.md) — how interpolation tokens deliver values into `config.toml` +- [Getting started](../getting-started.md) — the quickstart these defaults come from diff --git a/en/docs/api-portal/next/tutorials/secured-api-end-to-end.md b/en/docs/api-portal/next/tutorials/secured-api-end-to-end.md new file mode 100644 index 000000000..38bea8abc --- /dev/null +++ b/en/docs/api-portal/next/tutorials/secured-api-end-to-end.md @@ -0,0 +1,478 @@ +--- +title: "End to end: a secured API from gateway to portal" +description: "Stand up the control plane, portal, and gateway, publish an API that requires both a subscription token and an API key, and invoke it with credentials issued in the API Portal." +canonical_url: https://wso2.com/api-platform/docs/api-portal/tutorials/secured-api-end-to-end/ +md_url: https://wso2.com/api-platform/docs/api-portal/tutorials/secured-api-end-to-end.md +tags: + - cloud + - api-portal + - tutorials + - authentication +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-05 +content_type: "tutorial" +--- + +# End-to-end: a secured API from gateway to portal + +This tutorial connects three pieces: the Platform API control plane, a gateway, and the API Portal. You'll publish an API that requires both a subscription token and an API key on every call. Then you'll generate both credentials in the portal and watch the gateway accept them. + +The point of the exercise is the seam in the middle. The portal never talks to the gateway. When a developer subscribes or generates a key, the portal fires a **signed webhook** to the Platform API, which persists the credential and pushes it to every gateway where the API is deployed. Getting that seam right is most of the work, and most of this tutorial. + +## What you'll build + +```text +API Portal ──signed webhook──▶ Platform API ──control plane──▶ Gateway + (issues the (verifies, decrypts, (enforces + credentials) persists, broadcasts) on each call) + ▲ + consumer's request ───────────┘ + API-Key + Subscription-Key +``` + +## Prerequisites + +- Docker with the Compose plugin, `curl`, `unzip`, `jq`, and `openssl` +- Free ports: **9543** (portal), **9243** (Platform API), **9090** and **9094** (gateway management and admin), **8080** (gateway API listener) + +## Step 1: Start the control plane and portal + +The API Portal distribution ships the Platform API alongside it, so one compose file gives you both. + +```bash +curl -sLO https://github.com/wso2/api-platform/releases/download/api-portal%2Fv1.0.0/wso2apip-api-portal-1.0.0.zip +unzip wso2apip-api-portal-1.0.0.zip +cd wso2apip-api-portal-1.0.0 +./scripts/setup.sh +docker compose up -d +``` + +`setup.sh` provisions the TLS certificate, encryption keys, the RS256 JWT keypair the two services share, and your admin credentials. **Copy the admin password it prints**—it's shown once. + +Confirm both are up: + +```bash +curl -fsk https://localhost:9243/health && echo " platform-api ok" +curl -fsk -o /dev/null https://localhost:9543/api-portal/default/views/default && echo "api-portal ok" +``` + +Now get a Platform API token and create a project to hold the API. Every Platform API call below uses this token, and the portal accepts the same one—it verifies it against the shared public key. + +```bash +export ADMIN_USERNAME=admin +export ADMIN_PASSWORD='<the password setup.sh printed>' + +export TOKEN=$(curl -sk -X POST https://localhost:9243/api/portal/v0.9/auth/login \ + -d "username=$ADMIN_USERNAME&password=$ADMIN_PASSWORD" | jq -r .token) + +export PROJECT_ID=$(curl -sk -X POST https://localhost:9243/api/v0.9/projects \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ + -d '{"id":"my-project","displayName":"My Project","description":"My tutorial project"}' | jq -r .id) + +echo "project: $PROJECT_ID" +``` + +## Step 2: Register the gateway, then start it + +A gateway has to be registered with the Platform API before it can join. Registration returns an id; a second call mints the token the gateway authenticates with. + +```bash +export GW_NAME=my-gateway + +curl -sk -X POST https://localhost:9243/api/v0.9/gateways \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ + -d "{\"id\":\"$GW_NAME\",\"displayName\":\"$GW_NAME\",\"endpoints\":[\"http://localhost:8080\"],\"functionalityType\":\"regular\"}" | jq -r .id + +export GW_TOKEN=$(curl -sk -X POST https://localhost:9243/api/v0.9/gateways/$GW_NAME/tokens \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{}' | jq -r .token) +``` + +Download the gateway distribution and run its setup: + +```bash +cd .. +curl -sLO https://github.com/wso2/api-platform/releases/download/gateway%2Fv1.2.0-rc/wso2apip-api-gateway-1.2.0-rc.zip +unzip wso2apip-api-gateway-1.2.0-rc.zip +cd wso2apip-api-gateway-1.2.0-rc +./scripts/setup.sh +``` + +Point the controller at the control plane by adding these to `api-platform.env`, then start it: + +```bash +cat >> api-platform.env <<EOF +APIP_GW_CONTROLLER_CONTROLPLANE_HOST=host.docker.internal:9243 +APIP_GW_CONTROLLER_CONTROLPLANE_TOKEN=$GW_TOKEN +APIP_GW_CONTROLLER_CONTROLPLANE_GATEWAY_NAME=$GW_NAME +APIP_GW_CONTROLLER_CONTROLPLANE_INSECURE_SKIP_VERIFY=true +EOF + +docker compose up -d +curl -fs http://localhost:9094/api/admin/v1/health && echo " gateway ok" +``` + +`APIP_GW_CONTROLLER_CONTROLPLANE_GATEWAY_NAME` must match the id you registered, and `INSECURE_SKIP_VERIFY` is needed here only because `setup.sh` generated a self-signed certificate. `CONTROLPLANE_HOST` is a `host:port` with no scheme—the controller prepends `https://` and `wss://` itself, so adding `https://` here makes the connection fail with a `lookup https: no such host` error. + +!!! note + `host.docker.internal` lets the gateway containers reach the Platform API published on your host. On Linux without that alias, put both stacks on one Docker network and use the service name instead. + +## Step 3: Connect the portal's webhooks to the Platform API + +This is the seam. Two things have to be true before a portal-issued credential can reach the gateway. + +**First, link the portal's organization to the control plane.** The Platform API resolves each incoming event's organization by handle, read from `org.ref_id`, which comes from the portal organization's `cpRefId`: + +```bash +curl -sk -X PUT https://localhost:9543/api-portal/api/v0.9/organizations/default \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ + -d '{"id":"default","displayName":"Default","idpRefId":"default","cpRefId":"default"}' +``` + +The update requires `id`, `displayName`, and `idpRefId`. The `id` and `idpRefId` are fixed at seed time and can't change, so pass their existing values (both `default` here)—only `cpRefId` is new. + +You can set the same field from the portal UI instead: **Settings > Organization**, under the **ORGANIZATION** group, in the **Control plane reference ID** field. See [Organization settings](../admin-settings/organization-settings.md). + +**Second, register the Platform API as a webhook subscriber.** The `secret` here does double duty: it signs each delivery and derives the key that encrypts the credential fields. It must equal `APIP_CP_WEBHOOK_SECRET` on the Platform API, or signature verification and decryption both fail. + +```bash +export WEBHOOK_SECRET=$(openssl rand -hex 32) + +curl -sk -X POST https://localhost:9543/api-portal/api/v0.9/webhook-subscribers \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ + -d "{\"id\":\"platform-api\",\"displayName\":\"Platform API\", + \"targetUrl\":\"https://platform-api:9243/api/internal/v0.9/webhook/events\", + \"secret\":\"$WEBHOOK_SECRET\", + \"events\":[\"apikey.*\",\"subscription.*\"],\"enabled\":true}" +``` + +The Platform API's webhook receiver ships disabled, and the shipped `configs/config.toml` has no `[platform_api.webhook]` section wiring in a secret—setting `APIP_CP_WEBHOOK_SECRET` alone does nothing. Enable the receiver and point it at the same secret, then set the value in `api-platform.env` and restart the Platform API so both changes take effect: + +{% raw %} + +```bash +cd ../wso2apip-api-portal-1.0.0 + +cat >> configs/config.toml <<'EOF' + +[platform_api.webhook] +enabled = true +secret = '{{ env "APIP_CP_WEBHOOK_SECRET" }}' +EOF + +echo "APIP_CP_WEBHOOK_SECRET=$WEBHOOK_SECRET" >> api-platform.env +chmod 600 api-platform.env +docker compose up -d platform-api +``` + +{% endraw %} + +!!! important "Without this, every delivery fails with a plain 404" + The route isn't conditionally rejecting the request—it's never registered on the server at all while `enabled` is `false`, so any delivery attempt gets a bare 404 with no error detail to explain why. + +!!! warning + `api-platform.env` now holds a live shared secret, alongside the admin password hash. Keep it readable only by its owner, and never commit it to source control. + +`targetUrl` uses the container name because the portal reaches the Platform API across the Docker network, not through your host's published port. + +!!! note "Webhook deliveries fail with a TLS error" + The portal and the Platform API share a self-signed certificate, mounted into the `api-portal` container at `/etc/api-portal/tls/cert.pem`. Node doesn't trust it by default, so a delivery to `https://platform-api:9243/...` fails certificate verification until the portal process is told to trust that certificate. Point `NODE_EXTRA_CA_CERTS` at it and restart the portal: + + ```bash + echo "NODE_EXTRA_CA_CERTS=/etc/api-portal/tls/cert.pem" >> api-platform.env + docker compose up -d api-portal + ``` + +You can register the same subscriber from the portal UI instead: **Settings > Webhooks**, under **INTEGRATIONS**, with **+ Add webhook**. See [Webhook Integration](../admin-settings/webhook-integration.md). + +## Step 4: Create the secured API + +Create a subscription plan on the Platform API: + +```bash +export PLAN=gold + +curl -sk -X POST https://localhost:9243/api/v0.9/subscription-plans \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ + -d "{\"id\":\"$PLAN\",\"displayName\":\"$PLAN\",\"status\":\"ACTIVE\", + \"limits\":[{\"limitType\":\"REQUEST_COUNT\",\"timeUnit\":\"HOUR\",\"limitCount\":10000}]}" +``` + +!!! warning "Give each plan a unique display name" + The gateway stores plans keyed by gateway and **display name**. Two plans sharing one display name collide, and the second one—along with its subscriptions—silently fails to sync. + +Now the API. Two policies do the enforcing: `api-key-auth` reads the key from a header you name, and `subscription-validation` reads the subscription token from another. + +```bash +export API_ID=$(curl -sk -X POST https://localhost:9243/api/v0.9/rest-apis \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ + -d "{\"displayName\":\"Reading List API\",\"context\":\"/reading-list/\$version\",\"version\":\"v1\", + \"projectId\":\"$PROJECT_ID\",\"lifeCycleStatus\":\"PUBLISHED\", + \"subscriptionPlans\":[\"$PLAN\"], + \"upstream\":{\"main\":{\"url\":\"https://apis.bijira.dev/samples/reading-list-api-service/v1.0\"}}, + \"policies\":[ + {\"name\":\"api-key-auth\",\"version\":\"v1\", + \"params\":{\"key\":\"API-Key\",\"in\":\"header\"}}, + {\"name\":\"subscription-validation\",\"version\":\"v1\", + \"params\":{\"subscriptionKeyHeader\":\"Subscription-Key\"}} + ]}" | jq -r .id) +``` + +The header names are the gateway's, set here—`API-Key` and `Subscription-Key` are the policy defaults, and changing these params changes what consumers must send. + +Deploy it to the gateway, then confirm the route is live and enforcing. The deployment takes a `name` label, a `base` source (`current` deploys the latest working copy), and the target `gatewayId`: + +```bash +curl -sk -X POST https://localhost:9243/api/v0.9/rest-apis/$API_ID/deployments \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ + -d "{\"name\":\"v1.0\",\"base\":\"current\",\"gatewayId\":\"$GW_NAME\"}" + +curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/reading-list/v1/books +``` + +Expect **401** or **403**. A **404** means the route isn't programmed yet—wait a few seconds and retry. + +!!! important "Deploy before issuing credentials" + Do not create the subscription or key first. The Platform API only broadcasts credential events to gateways where the API is **already deployed**, and issuing an API key while no gateway is connected returns `503`. Once the API is deployed, credentials propagate live over the control-plane connection—no restart needed. + +## Step 5: Mirror the API and plan into the portal + +The Platform API resolves each event's API and plan by **handle**, so the portal's copies have to carry matching references. Together with the organization link from step 3, that's three linkages that must line up: + +| Portal field | Must equal | +|---|---| +| Organization `cpRefId` | The Platform API organization handle (`default`) | +| API `referenceId` | The Platform API API handle | +| Plan `refId` | The Platform API plan handle | + +Sync the plan: + +```bash +curl -sk -X PUT https://localhost:9543/api-portal/api/v0.9/subscription-plans \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ + -d "{\"id\":\"$PLAN\",\"displayName\":\"$PLAN\",\"refId\":\"$PLAN\", + \"limits\":[{\"limitType\":\"REQUEST_COUNT\",\"limitCount\":10000, + \"timeUnit\":\"HOUR\",\"timeAmount\":1}]}" +``` + +You can set the same `refId` from the portal UI instead: **Settings > Subscription Plans**, under **ORGANIZATION**, in the **External reference ID** field when adding or editing the plan. See [Subscription plans](../admin-settings/subscription-plans.md). + +Then publish the API to the portal with `referenceId` set to the Platform API handle, and its specification attached. Adding an API is also possible from **Settings > APIs**, under **CONTENT**, with **+ Add API**—see [Manage APIs](../admin-settings/manage-apis.md)—but that wizard has no field for `referenceId`, so this particular linkage still needs the manifest and the Management API. Create the manifest, setting `referenceId` to the value of `$API_ID` and listing `$PLAN` under `subscriptionPlans`: + +```bash +cat > api.yaml <<EOF +apiVersion: api-portal.api-platform.wso2.com/v1 +kind: RestApi + +metadata: + name: reading-list-api-v1 + +spec: + type: REST + displayName: Reading List API + version: v1 + description: Track a personal reading list. Every call requires an API key and a subscription token. + status: PUBLISHED + referenceId: $API_ID + + tags: + - reading-list + + labels: + - default + + subscriptionPlans: + - $PLAN + + agentVisibility: VISIBLE + + businessInformation: + businessOwner: Platform Owner + businessOwnerEmail: support@example.com + technicalOwner: API Team + technicalOwnerEmail: architecture@example.com + + endpoints: + sandboxUrl: http://localhost:8080/reading-list/v1 + productionUrl: http://localhost:8080/reading-list/v1 +EOF +``` + +Create the matching OpenAPI definition: + +```bash +cat > definition.yaml <<'EOF' +openapi: 3.0.1 +info: + title: Reading List API + version: v1 + description: | + Track a personal reading list — add books, update their reading status, and + remove them when you're done. Requires an API key and a subscription token. +servers: + - url: http://localhost:8080/reading-list/v1 +security: + - ApiKeyHeader: [] +components: + securitySchemes: + ApiKeyHeader: + type: apiKey + in: header + name: API-Key + parameters: + SubscriptionKeyHeader: + name: Subscription-Key + x-header-type: subscription-key + in: header + required: true + schema: + type: string + schemas: + Book: + type: object + required: [title, author, status] + properties: + id: + type: string + format: uuid + readOnly: true + title: + type: string + example: The Great Gatsby + author: + type: string + example: F. Scott Fitzgerald + status: + type: string + enum: [to_read, reading, read] +paths: + /books: + parameters: + - $ref: '#/components/parameters/SubscriptionKeyHeader' + get: + summary: List books + responses: + '200': + description: OK. The reading list. + post: + summary: Add a book + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Book' + responses: + '201': + description: Created. The newly added book. + /books/{id}: + parameters: + - $ref: '#/components/parameters/SubscriptionKeyHeader' + - name: id + in: path + required: true + schema: + type: string + format: uuid + get: + summary: Get a book + responses: + '200': + description: OK. The requested book. + put: + summary: Update a book + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Book' + responses: + '200': + description: OK. The updated book. + delete: + summary: Remove a book + responses: + '204': + description: No Content. The book was removed. +EOF +``` + +Publish both files: + +```bash +curl -sk -X POST https://localhost:9543/api-portal/api/v0.9/apis \ + -H "Authorization: Bearer $TOKEN" \ + -F "metadata=@api.yaml;type=application/yaml" \ + -F "definition=@definition.yaml;type=application/yaml" +``` + +Open `https://localhost:9543/api-portal/default/views/default/apis`. The API appears in the catalog with its plan. + +## Step 6: Get both credentials in the portal + +Do this part in the portal UI—it's what a developer would actually do. Sign in with the admin credentials. + +1. Open the API and click **Subscribe** on the plan. Copy the **subscription token** from the dialog. +2. Click **API Keys**, then **Generate API key**. Give it a name and copy the key. + +Each action fires a webhook the Platform API turns into gateway state. For the equivalent REST calls, see [Subscriptions](../rest-api/subscriptions.md) and [API Keys](../rest-api/api-keys.md). + +## Step 7: Invoke through the gateway + +Send both credentials, in the headers the policies named: + +```bash +export API_KEY='<the key from the portal>' +export SUB_TOKEN='<the subscription token from the portal>' + +curl -i http://localhost:8080/reading-list/v1/books \ + -H "API-Key: $API_KEY" \ + -H "Subscription-Key: $SUB_TOKEN" +``` + +A **200** means the whole chain worked: the portal issued the credentials, signed and encrypted them into a webhook, the Platform API verified and decrypted them, and the gateway is now enforcing them on live traffic. + +Propagation takes a moment. If you get a 401 or 403 immediately after generating the credentials, retry after a few seconds. + +### Confirm each credential is really being checked + +Drop one header at a time—each should be rejected: + +```bash +curl -s -o /dev/null -w 'no credentials: %{http_code}\n' http://localhost:8080/reading-list/v1/books +curl -s -o /dev/null -w 'key only: %{http_code}\n' http://localhost:8080/reading-list/v1/books -H "API-Key: $API_KEY" +curl -s -o /dev/null -w 'subscription only: %{http_code}\n' http://localhost:8080/reading-list/v1/books -H "Subscription-Key: $SUB_TOKEN" +``` + +## Step 8: Watch a lifecycle change propagate + +Credential changes travel the same path. In the portal, revoke the API key, then call again with it: + +```bash +curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/reading-list/v1/books \ + -H "API-Key: $API_KEY" -H "Subscription-Key: $SUB_TOKEN" +``` + +Once the webhook lands you get **401**—the key is gone from the gateway without anyone touching the gateway. Generate a new key and the call succeeds again. + +The same holds for the subscription side, where a rejection reads as **403** rather than 401: suspending the subscription blocks calls, resuming restores them, and regenerating the token invalidates the old one while the new one works. See [Manage Subscriptions](../consume-an-api/manage-subscriptions.md). + +## Troubleshooting + +| Symptom | Likely cause | +|---|---| +| `404` at the gateway | The route isn't programmed yet, or the API wasn't deployed to this gateway | +| `503` when generating an API key | No gateway is connected for that API—deploy it first | +| Credentials never start working | The webhook secret differs between the portal subscriber and `APIP_CP_WEBHOOK_SECRET`, so signatures fail and credential fields can't be decrypted | +| Only the subscription fails (`403`) | The portal plan's `refId` doesn't match the Platform API plan handle, or two plans share a display name | +| Every delivery gets a `404` | The Platform API's webhook receiver isn't enabled—add `[platform_api.webhook]` with `enabled = true` to `configs/config.toml` and restart it | +| Nothing arrives at all | The organization's `cpRefId` doesn't match the Platform API organization handle | +| Deliveries fail once and stop | Webhook delivery is attempted exactly once with no retry—check delivery history, see [Webhook Events](../rest-api/webhook-events.md) | + +## Related + +- [Webhook Event Catalog](../references/webhook-event-catalog.md): the events this flow depends on, their payloads, and the signing and encryption scheme +- [Webhook Integration](../admin-settings/webhook-integration.md): registering a subscriber from the Settings UI +- [Consume an API](../consume-an-api/overview.md): which credentials an API expects, and how to tell +- [Manage Subscriptions](../consume-an-api/manage-subscriptions.md) and [Manage API Keys](../consume-an-api/manage-api-keys.md): the consumer-side lifecycles +- [Getting Started](../getting-started.md): the portal on its own, without a gateway diff --git a/en/mkdocs.yml b/en/mkdocs.yml index dd17fd456..16131dd91 100644 --- a/en/mkdocs.yml +++ b/en/mkdocs.yml @@ -316,7 +316,7 @@ nav: - Devportal Mode: cloud/devportal/developer-portal-mode.md - AI Agent Discovery: cloud/devportal/discover-apis/ai-agent-discovery.md - Discover APIs: - - Search APIs: cloud/devportal/discover-apis/browse-apis.md + - Search APIs: cloud/devportal/discover-apis/api-search.md - Documentations: cloud/devportal/discover-apis/api-documentations.md - API Workflows: - Consuming API Workflows: cloud/devportal/api-workflows/consuming-api-workflows.md @@ -331,7 +331,7 @@ nav: - AI Assisted SDK Generation: cloud/devportal/sdk-generation/ai-assisted-sdk-generation.md - Admin Settings: - LLM Instructions: cloud/devportal/admin-settings/llm-instructions.md - - Managing API Workflows: cloud/devportal/admin-settings/manage-api-workflows.md + - Managing API Workflows: cloud/devportal/admin-settings/managing-api-workflows.md - API Manager: - Overview: api-manager/overview.md - Documentation: https://apim.docs.wso2.com/en/latest/ @@ -685,88 +685,169 @@ nav: - Moesif Analytics: ai-gateway/1.0.0/analytics/moesif-analytics.md - Analytics Header Filter: ai-gateway/1.0.0/analytics/analytics-header-filter.md # In-development 'next' doc sets, only shown in the nav while browsing a - # /next/ (or <component>/next/) URL — see extra.version_scoped_navs. - API Portal & MCP Hub: - - Overview: next/api-portal/overview.md - - About this Release: next/api-portal/about-this-release.md - - Concepts: next/api-portal/concepts.md - # Stand the portal up, in the order an operator does it. - - Getting Started: next/api-portal/getting-started.md - - Tutorials: - - Secured API End to End: next/api-portal/tutorials/secured-api-end-to-end.md - - Setting Up: - - Configuration & Interpolation: next/api-portal/setting-up/configuration.md - - Ports: next/api-portal/setting-up/ports.md - - Database: next/api-portal/setting-up/database.md - - Authentication: - - Overview: next/api-portal/setting-up/authentication/overview.md - - Connect an Identity Provider: next/api-portal/setting-up/authentication/connect-an-identity-provider.md - - Set up Asgardeo: next/api-portal/setting-up/authentication/asgardeo-as-idp.md - - Artifact Types: next/api-portal/setting-up/artifact-types.md - # What a consumer does in the portal, in the order they do it. - - Discover APIs: - - Overview: next/api-portal/discover-apis/overview.md - - Browse APIs: next/api-portal/discover-apis/browse-apis.md - - Discover MCP Servers: - - Overview: next/api-portal/mcp-servers/overview.md - - Browse MCP Servers: next/api-portal/mcp-servers/browse-mcp-servers.md - - Connect to an MCP Server: next/api-portal/mcp-servers/connect-to-an-mcp-server.md - - API Workflows: next/api-portal/api-workflows.md - - AI Agent Discovery: next/api-portal/ai-agent-discovery.md - - MCP Registry API: next/api-portal/mcp-registry.md - - Applications and Credentials: - - Which Credentials You Need: next/api-portal/consume-an-api/overview.md - - Manage Applications: next/api-portal/consume-an-api/manage-applications.md - - Manage Subscriptions: next/api-portal/consume-an-api/manage-subscriptions.md - - Manage API Keys: next/api-portal/consume-an-api/manage-api-keys.md - - Call an OAuth2-Secured API: next/api-portal/consume-an-api/oauth2.md - - Call an API-Key-Secured API: next/api-portal/consume-an-api/api-key.md - # Configure the portal at runtime, once it is serving. - - Admin Settings: - - Organization: - - Organization Settings: next/api-portal/admin-settings/organization-settings.md - - Manage Views: next/api-portal/admin-settings/manage-views.md - - Manage Labels: next/api-portal/admin-settings/manage-labels.md - - Subscription Plans: next/api-portal/admin-settings/subscription-plans.md - - Content: - - Manage APIs: next/api-portal/admin-settings/manage-apis.md - - Manage MCP Servers: next/api-portal/admin-settings/manage-mcp-servers.md - - Manage API Workflows: next/api-portal/admin-settings/manage-api-workflows.md - - API Content: next/api-portal/admin-settings/api-content.md - - LLM Instructions: next/api-portal/admin-settings/llm-instructions.md - - Integrations: - - Key Manager Integration: next/api-portal/admin-settings/key-manager-integration.md - - Webhook Integration: next/api-portal/admin-settings/webhook-integration.md - - Appearance: - - Theming: next/api-portal/admin-settings/theming.md - - Apply a Theme to a View: next/api-portal/admin-settings/apply-a-theme.md - - Design Mode: next/api-portal/admin-settings/design-mode.md - - References: - - Management API: - - Overview: next/api-portal/rest-api/overview.md - - Authentication: next/api-portal/rest-api/authentication.md - - Organizations: next/api-portal/rest-api/organizations.md - - Organization Content: next/api-portal/rest-api/organization-content.md - - Views: next/api-portal/rest-api/views.md - - Labels: next/api-portal/rest-api/labels.md - - Subscription Plans: next/api-portal/rest-api/subscription-plans.md - - Key Managers: next/api-portal/rest-api/key-managers.md - - APIs: next/api-portal/rest-api/apis.md - - API Content: next/api-portal/rest-api/api-content.md - - API Keys: next/api-portal/rest-api/api-keys.md - - MCP Servers: next/api-portal/rest-api/mcp-servers.md - - MCP Server Content: next/api-portal/rest-api/mcp-server-content.md - - MCP Server Keys: next/api-portal/rest-api/mcp-server-keys.md - - Applications: next/api-portal/rest-api/applications.md - - Application Keys: next/api-portal/rest-api/application-keys.md - - Subscriptions: next/api-portal/rest-api/subscriptions.md - - API Workflows: next/api-portal/rest-api/api-workflows.md - - Webhook Subscribers: next/api-portal/rest-api/webhook-subscribers.md - - Webhook Events: next/api-portal/rest-api/webhook-events.md - - Schemas: next/api-portal/rest-api/schemas.md - - Webhook Event Catalog: next/api-portal/references/webhook-event-catalog.md - - Get a Bearer Token via curl: next/api-portal/references/get-a-bearer-token-via-curl.md - - Configurations: next/api-portal/references/configurations.md + - "next": + - Overview: api-portal/next/overview.md + - About this Release: api-portal/next/about-this-release.md + - Concepts: api-portal/next/concepts.md + # Stand the portal up, in the order an operator does it. + - Getting Started: api-portal/next/getting-started.md + - Tutorials: + - Secured API End to End: api-portal/next/tutorials/secured-api-end-to-end.md + - Setting Up: + - Configuration & Interpolation: api-portal/next/setting-up/configuration.md + - Ports: api-portal/next/setting-up/ports.md + - Database: api-portal/next/setting-up/database.md + - Authentication: + - Overview: api-portal/next/setting-up/authentication/overview.md + - Connect an Identity Provider: api-portal/next/setting-up/authentication/connect-an-identity-provider.md + - Set up Asgardeo: api-portal/next/setting-up/authentication/asgardeo-as-idp.md + - Artifact Types: api-portal/next/setting-up/artifact-types.md + # What a consumer does in the portal, in the order they do it. + - Discover APIs: + - Overview: api-portal/next/discover-apis/overview.md + - Browse APIs: api-portal/next/discover-apis/browse-apis.md + - Discover MCP Servers: + - Overview: api-portal/next/mcp-servers/overview.md + - Browse MCP Servers: api-portal/next/mcp-servers/browse-mcp-servers.md + - Connect to an MCP Server: api-portal/next/mcp-servers/connect-to-an-mcp-server.md + - API Workflows: api-portal/next/api-workflows.md + - AI Agent Discovery: api-portal/next/ai-agent-discovery.md + - MCP Registry API: api-portal/next/mcp-registry.md + - Applications and Credentials: + - Which Credentials You Need: api-portal/next/consume-an-api/overview.md + - Manage Applications: api-portal/next/consume-an-api/manage-applications.md + - Manage Subscriptions: api-portal/next/consume-an-api/manage-subscriptions.md + - Manage API Keys: api-portal/next/consume-an-api/manage-api-keys.md + - Call an OAuth2-Secured API: api-portal/next/consume-an-api/oauth2.md + - Call an API-Key-Secured API: api-portal/next/consume-an-api/api-key.md + # Configure the portal at runtime, once it is serving. + - Admin Settings: + - Organization: + - Organization Settings: api-portal/next/admin-settings/organization-settings.md + - Manage Views: api-portal/next/admin-settings/manage-views.md + - Manage Labels: api-portal/next/admin-settings/manage-labels.md + - Subscription Plans: api-portal/next/admin-settings/subscription-plans.md + - Content: + - Manage APIs: api-portal/next/admin-settings/manage-apis.md + - Manage MCP Servers: api-portal/next/admin-settings/manage-mcp-servers.md + - Manage API Workflows: api-portal/next/admin-settings/manage-api-workflows.md + - API Content: api-portal/next/admin-settings/api-content.md + - LLM Instructions: api-portal/next/admin-settings/llm-instructions.md + - Integrations: + - Key Manager Integration: api-portal/next/admin-settings/key-manager-integration.md + - Webhook Integration: api-portal/next/admin-settings/webhook-integration.md + - Appearance: + - Theming: api-portal/next/admin-settings/theming.md + - Apply a Theme to a View: api-portal/next/admin-settings/apply-a-theme.md + - Design Mode: api-portal/next/admin-settings/design-mode.md + - References: + - Management API: + - Overview: api-portal/next/rest-api/overview.md + - Authentication: api-portal/next/rest-api/authentication.md + - Organizations: api-portal/next/rest-api/organizations.md + - Organization Content: api-portal/next/rest-api/organization-content.md + - Views: api-portal/next/rest-api/views.md + - Labels: api-portal/next/rest-api/labels.md + - Subscription Plans: api-portal/next/rest-api/subscription-plans.md + - Key Managers: api-portal/next/rest-api/key-managers.md + - APIs: api-portal/next/rest-api/apis.md + - API Content: api-portal/next/rest-api/api-content.md + - API Keys: api-portal/next/rest-api/api-keys.md + - MCP Servers: api-portal/next/rest-api/mcp-servers.md + - MCP Server Content: api-portal/next/rest-api/mcp-server-content.md + - MCP Server Keys: api-portal/next/rest-api/mcp-server-keys.md + - Applications: api-portal/next/rest-api/applications.md + - Application Keys: api-portal/next/rest-api/application-keys.md + - Subscriptions: api-portal/next/rest-api/subscriptions.md + - API Workflows: api-portal/next/rest-api/api-workflows.md + - Webhook Subscribers: api-portal/next/rest-api/webhook-subscribers.md + - Webhook Events: api-portal/next/rest-api/webhook-events.md + - Schemas: api-portal/next/rest-api/schemas.md + - Webhook Event Catalog: api-portal/next/references/webhook-event-catalog.md + - Get a Bearer Token via curl: api-portal/next/references/get-a-bearer-token-via-curl.md + - Configurations: api-portal/next/references/configurations.md + - "1.0.0": + - Overview: api-portal/1.0.0/overview.md + - About this Release: api-portal/1.0.0/about-this-release.md + - Concepts: api-portal/1.0.0/concepts.md + # Stand the portal up, in the order an operator does it. + - Getting Started: api-portal/1.0.0/getting-started.md + - Tutorials: + - Secured API End to End: api-portal/1.0.0/tutorials/secured-api-end-to-end.md + - Setting Up: + - Configuration & Interpolation: api-portal/1.0.0/setting-up/configuration.md + - Ports: api-portal/1.0.0/setting-up/ports.md + - Database: api-portal/1.0.0/setting-up/database.md + - Authentication: + - Overview: api-portal/1.0.0/setting-up/authentication/overview.md + - Connect an Identity Provider: api-portal/1.0.0/setting-up/authentication/connect-an-identity-provider.md + - Set up Asgardeo: api-portal/1.0.0/setting-up/authentication/asgardeo-as-idp.md + - Artifact Types: api-portal/1.0.0/setting-up/artifact-types.md + # What a consumer does in the portal, in the order they do it. + - Discover APIs: + - Overview: api-portal/1.0.0/discover-apis/overview.md + - Browse APIs: api-portal/1.0.0/discover-apis/browse-apis.md + - Discover MCP Servers: + - Overview: api-portal/1.0.0/mcp-servers/overview.md + - Browse MCP Servers: api-portal/1.0.0/mcp-servers/browse-mcp-servers.md + - Connect to an MCP Server: api-portal/1.0.0/mcp-servers/connect-to-an-mcp-server.md + - API Workflows: api-portal/1.0.0/api-workflows.md + - AI Agent Discovery: api-portal/1.0.0/ai-agent-discovery.md + - MCP Registry API: api-portal/1.0.0/mcp-registry.md + - Applications and Credentials: + - Which Credentials You Need: api-portal/1.0.0/consume-an-api/overview.md + - Manage Applications: api-portal/1.0.0/consume-an-api/manage-applications.md + - Manage Subscriptions: api-portal/1.0.0/consume-an-api/manage-subscriptions.md + - Manage API Keys: api-portal/1.0.0/consume-an-api/manage-api-keys.md + - Call an OAuth2-Secured API: api-portal/1.0.0/consume-an-api/oauth2.md + - Call an API-Key-Secured API: api-portal/1.0.0/consume-an-api/api-key.md + # Configure the portal at runtime, once it is serving. + - Admin Settings: + - Organization: + - Organization Settings: api-portal/1.0.0/admin-settings/organization-settings.md + - Manage Views: api-portal/1.0.0/admin-settings/manage-views.md + - Manage Labels: api-portal/1.0.0/admin-settings/manage-labels.md + - Subscription Plans: api-portal/1.0.0/admin-settings/subscription-plans.md + - Content: + - Manage APIs: api-portal/1.0.0/admin-settings/manage-apis.md + - Manage MCP Servers: api-portal/1.0.0/admin-settings/manage-mcp-servers.md + - Manage API Workflows: api-portal/1.0.0/admin-settings/manage-api-workflows.md + - API Content: api-portal/1.0.0/admin-settings/api-content.md + - LLM Instructions: api-portal/1.0.0/admin-settings/llm-instructions.md + - Integrations: + - Key Manager Integration: api-portal/1.0.0/admin-settings/key-manager-integration.md + - Webhook Integration: api-portal/1.0.0/admin-settings/webhook-integration.md + - Appearance: + - Theming: api-portal/1.0.0/admin-settings/theming.md + - Apply a Theme to a View: api-portal/1.0.0/admin-settings/apply-a-theme.md + - Design Mode: api-portal/1.0.0/admin-settings/design-mode.md + - References: + - Management API: + - Overview: api-portal/1.0.0/rest-api/overview.md + - Authentication: api-portal/1.0.0/rest-api/authentication.md + - Organizations: api-portal/1.0.0/rest-api/organizations.md + - Organization Content: api-portal/1.0.0/rest-api/organization-content.md + - Views: api-portal/1.0.0/rest-api/views.md + - Labels: api-portal/1.0.0/rest-api/labels.md + - Subscription Plans: api-portal/1.0.0/rest-api/subscription-plans.md + - Key Managers: api-portal/1.0.0/rest-api/key-managers.md + - APIs: api-portal/1.0.0/rest-api/apis.md + - API Content: api-portal/1.0.0/rest-api/api-content.md + - API Keys: api-portal/1.0.0/rest-api/api-keys.md + - MCP Servers: api-portal/1.0.0/rest-api/mcp-servers.md + - MCP Server Content: api-portal/1.0.0/rest-api/mcp-server-content.md + - MCP Server Keys: api-portal/1.0.0/rest-api/mcp-server-keys.md + - Applications: api-portal/1.0.0/rest-api/applications.md + - Application Keys: api-portal/1.0.0/rest-api/application-keys.md + - Subscriptions: api-portal/1.0.0/rest-api/subscriptions.md + - API Workflows: api-portal/1.0.0/rest-api/api-workflows.md + - Webhook Subscribers: api-portal/1.0.0/rest-api/webhook-subscribers.md + - Webhook Events: api-portal/1.0.0/rest-api/webhook-events.md + - Schemas: api-portal/1.0.0/rest-api/schemas.md + - Webhook Event Catalog: api-portal/1.0.0/references/webhook-event-catalog.md + - Get a Bearer Token via curl: api-portal/1.0.0/references/get-a-bearer-token-via-curl.md + - Configurations: api-portal/1.0.0/references/configurations.md - AI Workspace: - "next": - Overview: ai-workspace/next/overview.md @@ -963,22 +1044,22 @@ plugins: - redirects: redirect_maps: # API Portal: overview + documentation pages merged into Browse APIs - next/api-portal/discover-apis/api-overview.md: next/api-portal/discover-apis/browse-apis.md - next/api-portal/discover-apis/api-documentations.md: next/api-portal/discover-apis/browse-apis.md + api-portal/next/discover-apis/api-overview.md: api-portal/next/discover-apis/browse-apis.md + api-portal/next/discover-apis/api-documentations.md: api-portal/next/discover-apis/browse-apis.md # API Portal: files relocated to match their nav sections - next/api-portal/discover-apis/ai-agent-discovery.md: next/api-portal/ai-agent-discovery.md - next/api-portal/mcp-servers/mcp-registry.md: next/api-portal/mcp-registry.md - next/api-portal/theming.md: next/api-portal/admin-settings/theming.md - next/api-portal/artifact-types.md: next/api-portal/setting-up/artifact-types.md - next/api-portal/manage-applications.md: next/api-portal/consume-an-api/manage-applications.md - next/api-portal/manage-subscriptions.md: next/api-portal/consume-an-api/manage-subscriptions.md - next/api-portal/manage-api-keys.md: next/api-portal/consume-an-api/manage-api-keys.md - next/api-portal/setting-up/design-mode.md: next/api-portal/admin-settings/design-mode.md - next/api-portal/tutorials/asgardeo-as-idp.md: next/api-portal/setting-up/authentication/asgardeo-as-idp.md + api-portal/next/discover-apis/ai-agent-discovery.md: api-portal/next/ai-agent-discovery.md + api-portal/next/mcp-servers/mcp-registry.md: api-portal/next/mcp-registry.md + api-portal/next/theming.md: api-portal/next/admin-settings/theming.md + api-portal/next/artifact-types.md: api-portal/next/setting-up/artifact-types.md + api-portal/next/manage-applications.md: api-portal/next/consume-an-api/manage-applications.md + api-portal/next/manage-subscriptions.md: api-portal/next/consume-an-api/manage-subscriptions.md + api-portal/next/manage-api-keys.md: api-portal/next/consume-an-api/manage-api-keys.md + api-portal/next/setting-up/design-mode.md: api-portal/next/admin-settings/design-mode.md + api-portal/next/tutorials/asgardeo-as-idp.md: api-portal/next/setting-up/authentication/asgardeo-as-idp.md # API Portal: filenames aligned with page titles - next/api-portal/discover-apis/api-search.md: next/api-portal/discover-apis/browse-apis.md - next/api-portal/mcp-servers/discover-mcp-servers.md: next/api-portal/mcp-servers/browse-mcp-servers.md - next/api-portal/admin-settings/managing-api-workflows.md: next/api-portal/admin-settings/manage-api-workflows.md + api-portal/next/discover-apis/api-search.md: api-portal/next/discover-apis/browse-apis.md + api-portal/next/mcp-servers/discover-mcp-servers.md: api-portal/next/mcp-servers/browse-mcp-servers.md + api-portal/next/admin-settings/managing-api-workflows.md: api-portal/next/admin-settings/manage-api-workflows.md # AI/API Gateway versioning restructuring (per-product Cloud/Self-Hosted variants) ai-gateway/ai-gateway-rest-api/authentication.md: ai-gateway/1.1.0/ai-gateway-rest-api/authentication.md ai-gateway/analytics/analytics-header-filter.md: ai-gateway/1.1.0/analytics/analytics-header-filter.md @@ -1288,13 +1369,13 @@ plugins: # Developer Portal devportal/developer-portal-mode.md: cloud/devportal/developer-portal-mode.md devportal/admin-settings/llm-instructions.md: cloud/devportal/admin-settings/llm-instructions.md - devportal/admin-settings/manage-api-workflows.md: cloud/devportal/admin-settings/manage-api-workflows.md + devportal/admin-settings/manage-api-workflows.md: cloud/devportal/admin-settings/managing-api-workflows.md devportal/api-workflows/consuming-api-workflows.md: cloud/devportal/api-workflows/consuming-api-workflows.md devportal/consuming-services/consume-an-api-secured-with-api-key.md: cloud/devportal/consuming-services/consume-an-api-secured-with-api-key.md devportal/consuming-services/consume-an-api-secured-with-oauth2.md: cloud/devportal/consuming-services/consume-an-api-secured-with-oauth2.md devportal/discover-apis/ai-agent-discovery.md: cloud/devportal/discover-apis/ai-agent-discovery.md devportal/discover-apis/api-documentations.md: cloud/devportal/discover-apis/api-documentations.md - devportal/discover-apis/browse-apis.md: cloud/devportal/discover-apis/browse-apis.md + devportal/discover-apis/browse-apis.md: cloud/devportal/discover-apis/api-search.md devportal/manage-applications/create-an-application.md: cloud/devportal/manage-applications/create-an-application.md devportal/manage-subscriptions/subscribe-to-an-api.md: cloud/devportal/manage-subscriptions/subscribe-to-an-api.md # Event Gateway @@ -1365,6 +1446,11 @@ extra: # here is rendered with a version selector; its direct children must be the # version groups (titled with the version string). See nav-item.html / theme.js. versioned_sections: + API Portal & MCP Hub: + slug: api-portal + default: "1.0.0" + versions: + - "1.0.0" API Gateway: slug: api-gateway default: "1.1.0" @@ -1386,9 +1472,7 @@ extra: # rendered hidden; theme.js reveals them while the current URL contains the # version as a path segment (e.g. /next/api-portal/...). See # partials/nav-item.html and theme.js. - version_scoped_navs: - - title: API Portal & MCP Hub - version: "next" + version_scoped_navs: [] expanded_navs: - title: Cloud options: @@ -1415,6 +1499,7 @@ extra: - title: API Portal & MCP Hub options: - verticle-line + - divider # Root-level (versioned) copy only; 'level: 1' keeps the AI Workspace # section nested under Cloud rendering as a regular collapsed item. - title: AI Workspace From 1e83465b0289002f39e66d27d5341172e474b7d7 Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake <piumal1999@gmail.com> Date: Fri, 7 Aug 2026 14:45:50 +0530 Subject: [PATCH 09/11] Update idp doc --- .../setting-up/authentication/asgardeo-as-idp.md | 9 ++++++--- en/docs/api-portal/1.0.0/setting-up/ports.md | 12 ++---------- .../setting-up/authentication/asgardeo-as-idp.md | 9 ++++++--- en/docs/api-portal/next/setting-up/ports.md | 12 ++---------- 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/en/docs/api-portal/1.0.0/setting-up/authentication/asgardeo-as-idp.md b/en/docs/api-portal/1.0.0/setting-up/authentication/asgardeo-as-idp.md index ace46e5bd..9f051c6c4 100644 --- a/en/docs/api-portal/1.0.0/setting-up/authentication/asgardeo-as-idp.md +++ b/en/docs/api-portal/1.0.0/setting-up/authentication/asgardeo-as-idp.md @@ -66,7 +66,7 @@ The portal recognizes two personas: whoever administers it, and whoever consumes 2. Under the **Roles** tab, create an application role named `dp_admin` and another named `dp_subscriber`. 3. Assign `dp_admin` **only to administrators**, and `dp_subscriber` to regular users in each sub-organization that needs access. -Step 4 points both `[api_portal.auth.authorization.portal_roles]` entries at these same names, so one pair of Asgardeo roles drives both page access and Management API authorization. To change what either role grants, edit the portal's `role-to-scope-mapping.yaml`—see [Choose how privileges reach the token](connect-an-identity-provider.md#step-3-choose-how-privileges-reach-the-token). +Step 4 points both `[api_portal.auth.authorization.portal_roles]` entries at these same names, so one pair of Asgardeo roles drives both page access and Management API authorization. (The shipped `config.toml` ships `ap_admin`/`ap_subscriber` there, the role names the Platform API mints for local auth — this tutorial replaces them with the Asgardeo roles you just created.) To change what either role grants, edit the portal's `role-to-scope-mapping.yaml`—see [Choose how privileges reach the token](connect-an-identity-provider.md#step-3-choose-how-privileges-reach-the-token). !!! note Browser login sessions still pass through the per-operation scope check in role mode, which is the gap role mode exists to close: the session's own roles claim is what the portal expands to authorize each Management API request. @@ -141,7 +141,10 @@ display_name = "Acme" Set this before the portal first starts. The handle is what the portal writes into the organization's **IDP reference ID** when it seeds the organization row, and that field is fixed afterward—the Management API rejects a request that changes it. Changing the handle later means seeding a new organization. -The handle is also the URL slug in `/api-portal/{handle}/views/{viewName}`, so it appears in every portal URL. The portal normalizes it to lowercase, though the claim match itself tolerates any case. +The handle is also the URL slug in `/api-portal/{handle}/views/{viewName}`, so it appears in every portal URL, and the portal normalizes it to lowercase. + +!!! note "Match the claim value to the handle exactly" + Configure Asgardeo to emit `org_name` with the same value as the handle, character for character. The portal matches the claim against the organization's stored **IDP reference ID**, which is seeded from the handle and compared verbatim — so a claim of `Acme` does not match a handle of `acme`. With the two aligned, the login flow closes: @@ -186,7 +189,7 @@ Keep the claim names consistent between the Asgardeo token attributes and the `[ `mode = "role"` above needs no scope registration in Asgardeo, which is why this tutorial uses it. If you'd rather have Asgardeo issue the portal's `dp:*` scopes directly and set `mode = "scope"`, register them in your tenant first. A helper script does the registration through Asgardeo's own management APIs. -1. Create a new OIDC application in Asgardeo, for example named `DevPortal System`. +1. Create a new OIDC application in Asgardeo, for example named `API Portal System`. 2. Under **API Authorization**, add the **API Resource Management API** and the **Application Management API**. 3. Note its client ID and client secret. 4. Download the script and run it: diff --git a/en/docs/api-portal/1.0.0/setting-up/ports.md b/en/docs/api-portal/1.0.0/setting-up/ports.md index 2fdec2ac1..ffdad3c1d 100644 --- a/en/docs/api-portal/1.0.0/setting-up/ports.md +++ b/en/docs/api-portal/1.0.0/setting-up/ports.md @@ -55,22 +55,14 @@ So one variable moves the published port and the listener together — no Compos APIP_AP_SERVER_PORT=8443 ``` -2. Update `base_url` in `configs/config.toml`. **This does not follow the variable** — the shipped file writes it as a plain literal: +2. Update `base_url` in `configs/config.toml` to match: ```toml [api_portal.server] base_url = "https://localhost:8443" ``` - Leaving it at `9543` doesn't stop the portal from serving. It only affects the absolute URLs embedded in generated AI-agent prompts, which would point at a port nothing listens on. To drive it from the environment instead, replace the literal with the token the config template ships: - - {% raw %} - - ```toml - base_url = '{{ env "APIP_AP_SERVER_BASE_URL" "https://localhost:9543" }}' - ``` - - {% endraw %} + This is the origin the portal embeds in generated AI-agent prompts, so it has to name the port callers actually reach. 3. Recreate the containers, and open the portal on the new port: diff --git a/en/docs/api-portal/next/setting-up/authentication/asgardeo-as-idp.md b/en/docs/api-portal/next/setting-up/authentication/asgardeo-as-idp.md index ace46e5bd..9f051c6c4 100644 --- a/en/docs/api-portal/next/setting-up/authentication/asgardeo-as-idp.md +++ b/en/docs/api-portal/next/setting-up/authentication/asgardeo-as-idp.md @@ -66,7 +66,7 @@ The portal recognizes two personas: whoever administers it, and whoever consumes 2. Under the **Roles** tab, create an application role named `dp_admin` and another named `dp_subscriber`. 3. Assign `dp_admin` **only to administrators**, and `dp_subscriber` to regular users in each sub-organization that needs access. -Step 4 points both `[api_portal.auth.authorization.portal_roles]` entries at these same names, so one pair of Asgardeo roles drives both page access and Management API authorization. To change what either role grants, edit the portal's `role-to-scope-mapping.yaml`—see [Choose how privileges reach the token](connect-an-identity-provider.md#step-3-choose-how-privileges-reach-the-token). +Step 4 points both `[api_portal.auth.authorization.portal_roles]` entries at these same names, so one pair of Asgardeo roles drives both page access and Management API authorization. (The shipped `config.toml` ships `ap_admin`/`ap_subscriber` there, the role names the Platform API mints for local auth — this tutorial replaces them with the Asgardeo roles you just created.) To change what either role grants, edit the portal's `role-to-scope-mapping.yaml`—see [Choose how privileges reach the token](connect-an-identity-provider.md#step-3-choose-how-privileges-reach-the-token). !!! note Browser login sessions still pass through the per-operation scope check in role mode, which is the gap role mode exists to close: the session's own roles claim is what the portal expands to authorize each Management API request. @@ -141,7 +141,10 @@ display_name = "Acme" Set this before the portal first starts. The handle is what the portal writes into the organization's **IDP reference ID** when it seeds the organization row, and that field is fixed afterward—the Management API rejects a request that changes it. Changing the handle later means seeding a new organization. -The handle is also the URL slug in `/api-portal/{handle}/views/{viewName}`, so it appears in every portal URL. The portal normalizes it to lowercase, though the claim match itself tolerates any case. +The handle is also the URL slug in `/api-portal/{handle}/views/{viewName}`, so it appears in every portal URL, and the portal normalizes it to lowercase. + +!!! note "Match the claim value to the handle exactly" + Configure Asgardeo to emit `org_name` with the same value as the handle, character for character. The portal matches the claim against the organization's stored **IDP reference ID**, which is seeded from the handle and compared verbatim — so a claim of `Acme` does not match a handle of `acme`. With the two aligned, the login flow closes: @@ -186,7 +189,7 @@ Keep the claim names consistent between the Asgardeo token attributes and the `[ `mode = "role"` above needs no scope registration in Asgardeo, which is why this tutorial uses it. If you'd rather have Asgardeo issue the portal's `dp:*` scopes directly and set `mode = "scope"`, register them in your tenant first. A helper script does the registration through Asgardeo's own management APIs. -1. Create a new OIDC application in Asgardeo, for example named `DevPortal System`. +1. Create a new OIDC application in Asgardeo, for example named `API Portal System`. 2. Under **API Authorization**, add the **API Resource Management API** and the **Application Management API**. 3. Note its client ID and client secret. 4. Download the script and run it: diff --git a/en/docs/api-portal/next/setting-up/ports.md b/en/docs/api-portal/next/setting-up/ports.md index 2fdec2ac1..ffdad3c1d 100644 --- a/en/docs/api-portal/next/setting-up/ports.md +++ b/en/docs/api-portal/next/setting-up/ports.md @@ -55,22 +55,14 @@ So one variable moves the published port and the listener together — no Compos APIP_AP_SERVER_PORT=8443 ``` -2. Update `base_url` in `configs/config.toml`. **This does not follow the variable** — the shipped file writes it as a plain literal: +2. Update `base_url` in `configs/config.toml` to match: ```toml [api_portal.server] base_url = "https://localhost:8443" ``` - Leaving it at `9543` doesn't stop the portal from serving. It only affects the absolute URLs embedded in generated AI-agent prompts, which would point at a port nothing listens on. To drive it from the environment instead, replace the literal with the token the config template ships: - - {% raw %} - - ```toml - base_url = '{{ env "APIP_AP_SERVER_BASE_URL" "https://localhost:9543" }}' - ``` - - {% endraw %} + This is the origin the portal embeds in generated AI-agent prompts, so it has to name the port callers actually reach. 3. Recreate the containers, and open the portal on the new port: From 89f40093561b5a6d8709951334044369c81305d3 Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake <piumal1999@gmail.com> Date: Fri, 7 Aug 2026 14:48:36 +0530 Subject: [PATCH 10/11] Add deployment guidelines --- .../deployment/control-plane-connection.md | 100 ++++++++ .../deployment/database-configuration.md | 135 +++++++++++ .../1.0.0/deployment/deploy-and-verify.md | 173 ++++++++++++++ .../api-portal/1.0.0/deployment/overview.md | 162 +++++++++++++ .../1.0.0/deployment/resources-and-scaling.md | 184 +++++++++++++++ .../1.0.0/deployment/security-hardening.md | 214 ++++++++++++++++++ .../deployment/control-plane-connection.md | 100 ++++++++ .../next/deployment/database-configuration.md | 135 +++++++++++ .../next/deployment/deploy-and-verify.md | 173 ++++++++++++++ .../api-portal/next/deployment/overview.md | 162 +++++++++++++ .../next/deployment/resources-and-scaling.md | 184 +++++++++++++++ .../next/deployment/security-hardening.md | 214 ++++++++++++++++++ en/mkdocs.yml | 16 ++ 13 files changed, 1952 insertions(+) create mode 100644 en/docs/api-portal/1.0.0/deployment/control-plane-connection.md create mode 100644 en/docs/api-portal/1.0.0/deployment/database-configuration.md create mode 100644 en/docs/api-portal/1.0.0/deployment/deploy-and-verify.md create mode 100644 en/docs/api-portal/1.0.0/deployment/overview.md create mode 100644 en/docs/api-portal/1.0.0/deployment/resources-and-scaling.md create mode 100644 en/docs/api-portal/1.0.0/deployment/security-hardening.md create mode 100644 en/docs/api-portal/next/deployment/control-plane-connection.md create mode 100644 en/docs/api-portal/next/deployment/database-configuration.md create mode 100644 en/docs/api-portal/next/deployment/deploy-and-verify.md create mode 100644 en/docs/api-portal/next/deployment/overview.md create mode 100644 en/docs/api-portal/next/deployment/resources-and-scaling.md create mode 100644 en/docs/api-portal/next/deployment/security-hardening.md diff --git a/en/docs/api-portal/1.0.0/deployment/control-plane-connection.md b/en/docs/api-portal/1.0.0/deployment/control-plane-connection.md new file mode 100644 index 000000000..55cb032ef --- /dev/null +++ b/en/docs/api-portal/1.0.0/deployment/control-plane-connection.md @@ -0,0 +1,100 @@ +--- +title: "Control Plane Connection" +description: "How a standalone API Portal & MCP Hub deployment reaches gateways: registering a webhook subscriber against an existing Platform API, and the local-auth connection it does not need in production." +canonical_url: https://wso2.com/api-platform/docs/api-portal/deployment/control-plane-connection/ +md_url: https://wso2.com/api-platform/docs/api-portal/deployment/control-plane-connection.md +tags: + - cloud + - api-portal + - deployment + - platform-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Control Plane Connection + +The API Portal & MCP Hub is a standalone product. It has no runtime dependency on a control plane: it stores its own catalog, authenticates against your identity provider, and reaches gateways by emitting signed events to an endpoint you register. + +There are exactly two places a Platform API can enter the picture, and in a production deployment only the second one usually applies: + +| Use | Direction | Needed in production? | +|---|---|---| +| Local authentication | Portal → Platform API | **No.** Production delegates login to an identity provider. | +| Webhook subscriber | Portal → subscriber | Only if you want credentials propagated to gateways. | + +## Credential propagation + +The portal doesn't hold gateway-specific logic and has no fixed binding to a control plane. When a credential or plan changes, it emits a signed event to every subscriber registered for that organization. Whoever receives it decides what to do with it. + +If you already run a Platform API fronting your gateways, that is the natural subscriber: it verifies the signature, decrypts the credential, persists it, and pushes it out to every gateway serving the API. That Platform API belongs to your existing estate — you point the portal at it, rather than deploying one alongside the portal. + +A gateway that consumes the events itself can subscribe directly instead, as can a handler of your own. And if nothing subscribes, the portal still works: credentials are created and shown to developers, they just aren't propagated anywhere. + +!!! warning "No subscriber means silent non-propagation" + With no subscriber registered, credentials never reach a gateway and nothing reports an error — from the portal's point of view there was simply no one to notify. If developers report that a freshly generated key is rejected at the gateway, check the subscriber registration first. + +### Register the subscriber + +Registration is runtime state, held per organization in the portal's database — not chart configuration. Do it once after the first deployment, through **Settings → Webhooks** or the Management API. It survives upgrades, and it's per organization rather than per pod, so the replica count doesn't change anything. + +Two things have to agree: + +- **`targetUrl`** must be the receiver's webhook endpoint, reachable from the portal's network. For a Platform API, that is `/api/internal/v0.9/webhook/events` on its host. +- **`secret`** must equal the receiver's configured webhook secret — `APIP_CP_WEBHOOK_SECRET` on a Platform API. It does double duty, signing each delivery and deriving the key that encrypts credential fields, so a mismatch fails both signature verification and decryption. + +On a Platform API, the webhook receiver ships **disabled**. Enable it and give it the same secret, or deliveries are rejected at the far end while the portal records them as sent. See [Webhook integration](../admin-settings/webhook-integration.md). + +### Reaching a subscriber outside the cluster + +An existing Platform API is usually not in the portal's namespace, so `targetUrl` is whatever address is routable from the portal's pods — an in-cluster Service name if it happens to be co-located, otherwise its external hostname. Make sure any egress NetworkPolicy allows it; a blocked delivery retries and eventually lands in `FAILED` rather than failing loudly at registration time. + +### In a replicated deployment + +Every portal pod runs its own webhook dispatcher and delivery worker, all claiming from the same queue. The claim locks are what keep two pods from dispatching the same event — see [Overview](overview.md#what-makes-a-replica-interchangeable). + +A delivery whose pod dies mid-flight is not lost: one left `IN_FLIGHT` for more than five minutes is marked `FAILED` and re-enters the queue on the next cycle. That recovery sweep is scoped to the organization, so pods serving other organizations never reset each other's genuinely in-flight work. + +## Local authentication (development only) + +This is the portal's one and only outbound call to a Platform API, and production deployments don't make it. The built-in login form posts credentials to the Platform API's portal login endpoint and verifies the returned RS256 token against that component's public key: + +=== "Kubernetes" + + ```yaml + api-portal-ui: + config: + platformApi: + baseUrl: "" # "" derives the in-cluster URL + insecure: false + auth: + publicKeyPath: /etc/devportal/keys/jwt_public.pem + secrets: + hasPublicKey: true + ``` + + `baseUrl` must use the in-cluster Service name and **container** port, because the portal connects over the cluster network rather than through a published port or ingress. Pointing it at an externally published address makes every login fail while the Platform API itself looks healthy. + + `generate-secrets.sh` copies the public key out of the Platform API's own Secret, so the two stay on the same keypair. Set `hasPublicKey: false` when you have no local-auth path at all. + +=== "Virtual machine" + + ```toml + [api_portal.auth.local] + platform_api_url = "https://platform-api:9243" + public_key_path = "/etc/api-portal/keys/jwt_public.pem" + tls_skip_verify = false + ``` + + When the Platform API runs in the same Compose stack, use its service name and container port — the portal reaches it over the Compose network, not through a published host port. `setup.sh` writes the keypair to `resources/keys/`, and the compose file mounts the public half into the portal. + +Leave `platform_api_url` empty and local authentication is disabled outright. + +Two details matter if you do run it. Skipping TLS verification on that hop (`insecure` / `tls_skip_verify`) exists for local development against a self-signed certificate and belongs nowhere near production. And the public key is what makes the portal verify the token rather than trust the transport — if it's missing, bearer-token requests fail closed rather than being accepted unverified. In `idp` mode tokens are verified against your identity provider's JWKS endpoint instead, and the key goes unused. + +## Related + +- [Webhook integration](../admin-settings/webhook-integration.md): registering and verifying subscribers +- [Authentication](../setting-up/authentication/overview.md): why production uses an identity provider +- [Security Hardening](security-hardening.md): the identity-provider configuration that replaces local auth diff --git a/en/docs/api-portal/1.0.0/deployment/database-configuration.md b/en/docs/api-portal/1.0.0/deployment/database-configuration.md new file mode 100644 index 000000000..9aa914185 --- /dev/null +++ b/en/docs/api-portal/1.0.0/deployment/database-configuration.md @@ -0,0 +1,135 @@ +--- +title: "Database Configuration" +description: "Provision the database for a production API Portal & MCP Hub deployment: schema, password Secret, Helm values, TLS verification, and connection-pool sizing across replicas." +canonical_url: https://wso2.com/api-platform/docs/api-portal/deployment/database-configuration/ +md_url: https://wso2.com/api-platform/docs/api-portal/deployment/database-configuration.md +tags: + - cloud + - api-portal + - deployment + - database +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Database Configuration + +The database is what makes a replicated deployment possible: it holds the catalog, applications, subscriptions, credentials, **user sessions**, and the webhook event queue. Every portal pod reads and writes the same one, which is why no pod needs sticky sessions or a shared filesystem. + +This page covers the production-specific setup. For driver choice and the schema itself, see [Set up the database](../setting-up/database.md). + +Use PostgreSQL or SQL Server. The examples below show PostgreSQL; [Set up the database](../setting-up/database.md) has the equivalents for SQL Server, including the driver values and port. + +## Create the database + +Provision an external, highly available server the cluster can reach, and create a database and a login role for the portal: + +```sql +CREATE DATABASE api_portal; +CREATE USER api_portal WITH PASSWORD '<strong-password>'; +GRANT ALL PRIVILEGES ON DATABASE api_portal TO api_portal; +``` + +The portal owns this database outright. If another component of your estate has its own store, keep them separate — the portal never reads or writes anyone else's. + +## Apply the schema + +Apply the schema before first start: + +```bash +psql -h postgres.example.com -U api_portal -d api_portal -f database/schema.postgres.sql +``` + +A reference copy ships in the distribution under `resources/api-portal/db-scripts/`, and the same file is bundled inside the image. See [Apply the schema](../setting-up/database.md#apply-the-schema-for-postgresql-or-microsoft-sql-server). + +## Store the password in a Secret + +The password never appears in a values file. It comes from the release's Secret, under the key `APIP_AP_DATABASE_PASSWORD` — the same name as the environment variable the container receives. `generate-secrets.sh` prompts for it and writes it into the Secret it creates. See [Security Hardening → Secrets](security-hardening.md#secrets). + +## Configure the chart + +=== "Kubernetes" + + ```yaml + api-portal-ui: + config: + database: + type: postgres + host: postgres.example.com + port: 5432 + database: api_portal # the schema name — rendered into config as `name` + user: api_portal + sslmode: verify-full # disable | require | verify-ca | verify-full + sslRootCert: /etc/api-portal/certs/ca.pem + ``` + + Two key names catch people out: the schema name is `database`, not `name`, and `sslmode` is all lowercase. + + Switching `type` off `sqlite` is also what unlocks scaling — the chart refuses to render an autoscaler on a database that can't be shared. + +=== "Virtual machine" + + The shipped `configs/config.toml` carries only `driver` and `path`, so **setting `APIP_AP_DATABASE_HOST` on its own does nothing** — an environment value reaches a setting only where a token references it. Replace the whole `[api_portal.database]` block with the form for your driver from `configs/config-template.toml`: + + {% raw %} + + ```toml + [api_portal.database] + driver = '{{ env "APIP_AP_DATABASE_DRIVER" "postgres" }}' + host = '{{ env "APIP_AP_DATABASE_HOST" "localhost" }}' + port = '{{ env "APIP_AP_DATABASE_PORT" "5432" }}' + name = '{{ env "APIP_AP_DATABASE_NAME" "api_portal" }}' + user = '{{ env "APIP_AP_DATABASE_USER" "" }}' + password = '{{ env "APIP_AP_DATABASE_PASSWORD" "" }}' + ssl_mode = "verify-full" + ssl_root_cert = "/etc/api-portal/tls/ca.pem" + ``` + + {% endraw %} + + Then set the values in `api-platform.env` on each VM. Note the TOML keys are snake_case (`ssl_mode`, `ssl_root_cert`) where the Helm values are not. + + `user` has no fallback on purpose: a non-SQLite driver with no user, host, or name refuses to start rather than silently connecting as a conventional superuser name to whatever server the defaults point at. + +## TLS to the database + +Use `verify-full` in production. It is the only mode that validates both the certificate chain and the hostname, so it defends against a redirected connection rather than merely encrypting one: + +| Mode | What it checks | +|---|---| +| `disable` | Nothing — cleartext | +| `require` | Encryption only; accepts any certificate | +| `verify-ca` | Certificate chains to a trusted CA | +| `verify-full` | Chain **and** that the hostname matches the certificate | + +`verify-ca` and `verify-full` need `sslRootCert` pointing at the CA certificate, mounted into the pod. + +## Connection pool tuning + +Each pod opens its own pool, so the cluster's total connection demand is: + +``` +replicas × maxOpenConns +``` + +With the default `maxOpenConns: 50`, three replicas ask for up to 150 connections. Keep the total comfortably below the server's `max_connections`, leaving headroom for administrative sessions and for a rolling upgrade — during which old and new pods briefly overlap. + +```yaml +api-portal-ui: + config: + database: + maxOpenConns: 25 + minOpenConns: 2 + poolIdleTimeoutMs: 10000 + poolConnectionTimeoutMs: 30000 + poolRequestTimeoutMs: 30000 +``` + +Lowering `maxOpenConns` as you raise the replica count is usually the right trade: more pods each holding a smaller pool. `minOpenConns` keeps a floor of warm connections per pod, so the first request after an idle period doesn't pay connection setup. + +## Related + +- [Set up the database](../setting-up/database.md): driver choice, the schema, and single-instance setup +- [Resources & Scaling](resources-and-scaling.md): choosing a replica count to size the pool against +- [Security Hardening](security-hardening.md): where the password lives diff --git a/en/docs/api-portal/1.0.0/deployment/deploy-and-verify.md b/en/docs/api-portal/1.0.0/deployment/deploy-and-verify.md new file mode 100644 index 000000000..f947bda13 --- /dev/null +++ b/en/docs/api-portal/1.0.0/deployment/deploy-and-verify.md @@ -0,0 +1,173 @@ +--- +title: "Deploy & Verify" +description: "Install the API Portal & MCP Hub on Kubernetes or virtual machines, confirm instances came up healthy and share sessions, and follow a safe upgrade procedure." +canonical_url: https://wso2.com/api-platform/docs/api-portal/deployment/deploy-and-verify/ +md_url: https://wso2.com/api-platform/docs/api-portal/deployment/deploy-and-verify.md +tags: + - cloud + - api-portal + - deployment + - kubernetes +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Deploy & Verify + +Work through [Security Hardening](security-hardening.md), [Database Configuration](database-configuration.md), and [Resources & Scaling](resources-and-scaling.md) first — the schema applied, the secrets provisioned, and the configuration assembled. This page installs the result and confirms it. + +## Provision the secrets + +=== "Kubernetes" + + The chart refuses to render without them, so this comes first: + + ```bash + ./generate-secrets.sh api-portal my-release # <namespace> [release-name] + ``` + + It writes `values-secrets.yaml`, naming each Secret it created along with the `has*` flags for whichever optional keys it provisioned. Keep that file alongside your own values and pass both at install time. + +=== "Virtual machine" + + Run the setup script once, on the **first** VM only: + + ```bash + ./scripts/setup.sh + ``` + + It provisions the TLS pair under `resources/certificates/` and the portal's encryption key and session secret under `resources/keys/`, and writes `.env` and `api-platform.env`. + + Then copy the generated material to every other VM before starting them, so all instances share it: + + ```bash + rsync -a resources/keys/ resources/certificates/ <other-vm>:<install-dir>/resources/ + ``` + + Do **not** run `setup.sh` independently on each VM — it would generate a different key and session secret per host, which is the failure described in [Security Hardening](security-hardening.md#secrets). + +## Deploy + +=== "Kubernetes" + + The `api-portal` chart pulls its components as OCI dependencies, so fetch them before installing: + + ```bash + helm dependency update ./api-portal-helm-chart + ``` + + Then install with your production values last, so they win: + + ```bash + helm install my-release ./api-portal-helm-chart \ + --namespace api-portal --create-namespace \ + -f values-secrets.yaml \ + -f values-production.yaml + ``` + + Render before you install if you want to see what a values change actually produces — this is also where the chart's fail-fast guards fire, before anything is created: + + ```bash + helm template my-release ./api-portal-helm-chart \ + -f values-secrets.yaml -f values-production.yaml > /dev/null + ``` + + A missing `secrets.existingSecret` or `tls.certificateProvider: selfSigned` fails here with an explicit message. + +=== "Virtual machine" + + On each VM, from the distribution directory: + + ```bash + docker compose up -d + ``` + + `COMPOSE_PROFILES` in `.env` decides what starts. For a standalone portal against an external database, that is `api-portal` alone. + + Check the configuration was read as you intended before putting the VM into the load balancer pool — the startup log reports the resolved database driver and the authorization mode: + + ```bash + docker compose logs api-portal | head -40 + ``` + +## Verify + +Confirm every instance is serving, not just that the deploy command succeeded. + +=== "Kubernetes" + + ```bash + kubectl -n api-portal get pods -l app.kubernetes.io/name=api-portal-ui + kubectl -n api-portal get hpa,pdb + ``` + + Check the pods landed on different nodes — a topology-spread constraint that couldn't be satisfied leaves a pod Pending: + + ```bash + kubectl -n api-portal get pods -o wide -l app.kubernetes.io/name=api-portal-ui + ``` + + Confirm the portal is serving, from inside the cluster: + + ```bash + kubectl -n api-portal exec deploy/my-release-api-portal-ui -- \ + curl -sk https://localhost:9543/health + ``` + +=== "Virtual machine" + + On each VM: + + ```bash + docker compose ps + curl -sk https://localhost:9543/health + ``` + + Then confirm the load balancer sees every instance as healthy, and that its health check is pointed at `/health` rather than `/`. + +Then verify the two things a single-instance smoke test cannot catch, on either substrate: + +**Sessions are shared.** Log in through the load balancer or ingress, then stop the instance that served the login. The next request should land on another instance with the session intact. If you are logged out instead, the instances are not reading the same session table — check that every instance points at the same external database and resolves the same encryption key and session secret. + +**Only one instance dispatches each event.** Trigger a credential change and confirm the registered subscriber receives exactly one delivery, not one per instance. See [Control Plane Connection](control-plane-connection.md#in-a-replicated-deployment). + +## Upgrade + +=== "Kubernetes" + + ```bash + helm dependency update ./api-portal-helm-chart + helm diff upgrade my-release ./api-portal-helm-chart \ + -f values-secrets.yaml -f values-production.yaml # requires the helm-diff plugin + helm upgrade my-release ./api-portal-helm-chart \ + -f values-secrets.yaml -f values-production.yaml + ``` + + **Always pass the secrets values file.** Omitting it on an upgrade fails the render rather than silently dropping the Secret reference — intended behavior, but it does mean an upgrade command is no shorter than an install command. + + Roll back a bad release with `helm rollback my-release`. + +=== "Virtual machine" + + Upgrade one VM at a time so the others keep serving: + + 1. Remove the VM from the load balancer pool. + 2. Update the image tag in `docker-compose.yaml`, then `docker compose up -d` to recreate the container. + 3. Confirm `https://localhost:9543/health` responds, and check the startup log. + 4. Return it to the pool, then move to the next VM. + + Re-running `setup.sh` is safe — it fills in only what is missing and never overwrites an existing value — so an upgrade won't rotate the encryption key out from under stored credentials. + + Roll back by putting the previous image tag back and recreating the container. + +Two things hold on both substrates: + +- **Old and new instances overlap during a rolling upgrade**, so peak database connections briefly exceed `instances × max_open_conns`. Leave headroom for it. +- **A rollback reverts configuration, not database schema changes.** Check the release notes before upgrading across a version that migrates the schema. + +## Related + +- [Security Hardening](security-hardening.md): what the secrets provisioning covers +- [Resources & Scaling](resources-and-scaling.md): the instance count and health checks installed here +- [Control Plane Connection](control-plane-connection.md): the post-install webhook registration diff --git a/en/docs/api-portal/1.0.0/deployment/overview.md b/en/docs/api-portal/1.0.0/deployment/overview.md new file mode 100644 index 000000000..984e9ae3d --- /dev/null +++ b/en/docs/api-portal/1.0.0/deployment/overview.md @@ -0,0 +1,162 @@ +--- +title: "Production deployment" +description: "Deploy the API Portal & MCP Hub in production on Kubernetes with Helm or on virtual machines, with an external database and, where you need it, replicated instances." +canonical_url: https://wso2.com/api-platform/docs/api-portal/deployment/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/deployment/overview.md +tags: + - cloud + - api-portal + - deployment + - kubernetes + - devops +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Production deployment + +This section covers running the API Portal & MCP Hub in production — on Kubernetes with Helm, or on virtual machines with the Compose distribution. Most of it applies whether you run one instance or several: the hardening, the database setup, and the gateway integration are the same either way. High availability is the one dimension that adds requirements, and they are set out below. + +The portal is a standalone product. A production deployment needs the portal and a database — nothing else. It authenticates users against your identity provider and reaches gateways by emitting signed webhook events to whatever endpoint you register, so it carries no runtime dependency on a control plane. + +Every page here carries **Kubernetes** and **Virtual machine** tabs wherever the mechanics differ. The requirements and failure modes are the same on both; only the delivery changes. + +The single-instance walkthrough in [Getting started](../getting-started.md) is the right shape for evaluation and development, not for production. + +## Running more than one instance + +Replication is what turns a production deployment into a highly available one. What it requires depends on your database. + +A production deployment runs against an external database — PostgreSQL or SQL Server. Both support replication, so either works whether you run one instance or several. + +!!! note "Change the shipped default" + The distribution ships pointing at SQLite so the quickstart works with no external dependency. That is an evaluation default; move to PostgreSQL or SQL Server before you deploy. See [Database Configuration](database-configuration.md). + +A single production instance is a legitimate deployment — it just has no redundancy, so every restart, upgrade, and host failure is an outage. Everything in this section except [Resources & Scaling](resources-and-scaling.md) applies to it unchanged. + +## Prerequisites + +=== "Kubernetes" + + | Tool | Requirement | + |------|-------------| + | `kubectl` | Configured against your target cluster | + | `helm` | Version 3+ | + | `openssl` | Available in your local shell, to generate keys | + | Database | An external, highly available PostgreSQL or SQL Server the cluster can reach | + + ```bash + kubectl cluster-info + kubectl get nodes + helm version + ``` + + The `api-portal` chart is the product package; values for the portal itself nest under the `api-portal-ui` key. It also carries the Platform API as an optional dependency, on by default for the local-auth quickstart. A deployment that authenticates against an identity provider doesn't need it: + + ```yaml + platform-api: + enabled: false + ``` + +=== "Virtual machine" + + | Requirement | Detail | + |---|---| + | Two or more VMs | Each running Docker with the Compose plugin | + | Load balancer | Fronting the VMs, health-checking `/health` | + | `openssl` | Available on the VM you provision secrets from | + | Database | An external, highly available PostgreSQL or SQL Server every VM can reach | + + Each VM gets its own copy of the distribution. The bundled Platform API and the SQLite default are both switched off for a production deployment — see [Resources & Scaling](resources-and-scaling.md#instance-count) and [Database Configuration](database-configuration.md). + +## Architecture + +A production deployment consists of two components: + +| Component | Description | +|---|---| +| **API Portal & MCP Hub** | Serves the catalog, the admin UI, and the Management API. Stateless apart from what it writes to the database, so it replicates freely once that database is shared. | +| **Database** | The shared source of truth: catalog artifacts, applications, subscriptions, credentials, user sessions, and the webhook event queue. | + +Two things sit outside the deployment and are integrations rather than components: your **identity provider**, which the portal redirects to for login, and a **webhook subscriber**, which receives the signed credential events the portal emits. Most estates point the subscriber at an existing Platform API that already fronts their gateways — but that Platform API belongs to that estate, not to this deployment, and the portal neither installs nor manages it. See [Control Plane Connection](control-plane-connection.md). + +Portal replicas hold no state of their own. Every request can land on any pod, which is what makes a plain round-robin Service sufficient — no sticky sessions, no shared filesystem. + +### What makes a replica interchangeable + +Two things, each worth knowing because each one breaks in a recognizable way if you get the configuration wrong: + +**Sessions live in the database, not in memory.** The portal writes sessions through a SQL session store shared by every dialect, so a user who logs in against one instance stays logged in when the next request lands on another. + +**Webhook dispatch is claimed with row locks.** Each replica runs its own webhook dispatcher and delivery worker. They claim work with `SELECT ... FOR UPDATE SKIP LOCKED`, scoped to the organization, so two replicas never dispatch the same event. Claims that die mid-flight are recovered: a delivery stuck `IN_FLIGHT` for more than five minutes is marked `FAILED` and re-enters the queue on the next cycle. + +## Topology + +Run at least two instances, in different failure domains — separate worker nodes on Kubernetes, separate availability zones or hypervisor hosts on VMs. Two instances sharing a domain survive a process crash but not the loss of that domain. See [Resources & Scaling](resources-and-scaling.md#spread-across-failure-domains). + +## Fronting the portal + +Nothing in the deployment provisions a public entry point — bring your own ingress controller or load balancer. Two portal-specific details matter, on either substrate: + +- **The portal serves under the `/api-portal` path prefix.** The origin root only redirects there. Route `/api-portal/*` through, and don't strip the prefix — the session cookie is scoped to that exact path, and a mismatch is a silent login loop rather than a visible error. +- **Set the public base URL.** It is what the portal embeds in generated AI-agent prompts. Behind a proxy it can't be inferred from the request, and a stale value produces prompts pointing at an unreachable host. + +=== "Kubernetes" + + The chart deploys a `Service`, not an Ingress. + + ```yaml + api-portal-ui: + config: + server: + baseUrl: https://portal.example.com + ``` + +=== "Virtual machine" + + Point the load balancer at port `9543` on each VM, with its health check on `/health`. + + ```toml + [api_portal.server] + base_url = "https://portal.example.com" + ``` + + Set it explicitly whenever the portal sits behind a proxy — it cannot be inferred from the request. See [Change the ports the API Portal uses](../setting-up/ports.md). + +Sessions live in the shared database, so no sticky-session configuration is required in either case. + +For the portal's own HTTPS listener, see [Security Hardening → TLS configuration](security-hardening.md#tls-configuration). + +## Setup steps + +Work through these in order — each builds on the last, and the Kubernetes chart's fail-fast guards assume the earlier ones are done: + +1. **[Security Hardening](security-hardening.md)** — provision the secrets, supply a real TLS certificate, switch authentication to your identity provider, and tighten the try-it proxy. +2. **[Database Configuration](database-configuration.md)** — create the database, apply the schema, and size the connection pool against your instance count. +3. **[Resources & Scaling](resources-and-scaling.md)** — set resources, spread instances across failure domains, and configure health checks. Skip to the next step if you are running a single instance. +4. **[Deploy & Verify](deploy-and-verify.md)** — install, then confirm the deployment is serving. +5. **[Control Plane Connection](control-plane-connection.md)** — register the webhook subscriber so credentials reach your gateways. + +## Production checklist + +Before you take traffic: + +- [ ] The database is an external, highly available PostgreSQL or SQL Server, with full TLS verification on +- [ ] The encryption key and session secret are identical on every instance +- [ ] Connection pool sized for `instances × max_open_conns` +- [ ] No `[api_portal.design_mode]` block in `config.toml` — that is a local authoring mode, not a deployment option +- [ ] Authentication mode is `idp`, with the client secret delivered out of band +- [ ] Authorization is enabled +- [ ] The public base URL matches the real origin, and the proxy preserves `/api-portal` +- [ ] The TLS certificate is a real one, not the self-signed pair from setup +- [ ] Instances spread across failure domains, with a health check on `/health` + +Kubernetes adds two: the PodDisruptionBudget is enabled, and CPU requests are set if the HPA is on. + +## Related + +- [Set up the database](../setting-up/database.md): schema provisioning, TLS, and connection pooling +- [Configuration and environment interpolation](../setting-up/configuration.md): how secrets reach `config.toml` without being written into it +- [Change the ports the API Portal uses](../setting-up/ports.md): moving the stack off its defaults +- [Connect an identity provider](../setting-up/authentication/connect-an-identity-provider.md): the production authentication path diff --git a/en/docs/api-portal/1.0.0/deployment/resources-and-scaling.md b/en/docs/api-portal/1.0.0/deployment/resources-and-scaling.md new file mode 100644 index 000000000..4fd6eb8f5 --- /dev/null +++ b/en/docs/api-portal/1.0.0/deployment/resources-and-scaling.md @@ -0,0 +1,184 @@ +--- +title: "Resources & Scaling" +description: "Size and scale a replicated API Portal & MCP Hub deployment on Kubernetes or virtual machines: instance count, resources, spreading across failure domains, and health checks." +canonical_url: https://wso2.com/api-platform/docs/api-portal/deployment/resources-and-scaling/ +md_url: https://wso2.com/api-platform/docs/api-portal/deployment/resources-and-scaling.md +tags: + - cloud + - api-portal + - deployment + - kubernetes +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Resources & Scaling + +The portal is a Node.js application that renders pages and serves the Management API. It holds no state of its own, so scaling it out is a matter of instance count — once the database is a shared one. + +Two is the practical minimum for availability: one instance means every restart, upgrade, and host failure is an outage. Anything above one requires the external database from [Database Configuration](database-configuration.md). + +## Instance count + +=== "Kubernetes" + + ```yaml + api-portal-ui: + deployment: + replicaCount: 2 + ``` + +=== "Virtual machine" + + Run the portal on two or more VMs, each with its own copy of the distribution, all pointed at the same external database and fronted by a load balancer. + + Drop `platform-api` from the `COMPOSE_PROFILES` line in `.env` on each VM so only the portal runs — an external or already-existing Platform API, if you use one at all, is not part of this stack: + + ``` + COMPOSE_PROFILES=api-portal + ``` + + No sticky sessions are needed on the load balancer: sessions live in the shared database, so any VM can serve any request. Plain round-robin is correct. + +## Resources + +Memory is the resource to watch. Uploads and archive extraction are bounded by the ceilings in [Security Hardening](security-hardening.md#upload-ceilings) — up to 50 MiB of extracted content per archive by default — so an instance handling a theme upload transiently needs more than its steady-state footprint. Size the ceiling above that peak, not against the idle figure. + +=== "Kubernetes" + + Set requests on every production pod. Without a CPU request the scheduler can't place pods sensibly and the HPA can't compute utilization at all: + + ```yaml + api-portal-ui: + deployment: + resources: + requests: + cpu: 500m + memory: 512Mi + limits: + memory: 1Gi + ``` + + Prefer leaving `limits.cpu` unset. A CPU limit throttles rather than kills, and throttling a single-threaded event loop turns a brief burst into queued latency across every request that pod is serving. + +=== "Virtual machine" + + Give each VM at least 2 vCPU and 2 GiB of RAM, then bound the container itself in `docker-compose.yaml` so a runaway upload can't take the host with it: + + ```yaml + services: + api-portal: + mem_limit: 1g + ``` + + Leave CPU unconstrained for the same reason Kubernetes deployments should skip `limits.cpu` — throttling a single-threaded event loop converts a burst into queued latency. + +## Spread across failure domains + +Instances that share a failure domain buy nothing when that domain fails. + +=== "Kubernetes" + + ```yaml + api-portal-ui: + deployment: + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule + labelSelector: + matchLabels: + app.kubernetes.io/name: api-portal-ui + ``` + + `DoNotSchedule` is deliberate: it leaves a pod Pending rather than co-locating it, which surfaces an under-provisioned cluster as a visible unscheduled pod instead of a silently fragile deployment. Use `ScheduleAnyway` only if you would rather have a degraded placement than a Pending pod. + + For spreading across availability zones, add `topology.kubernetes.io/zone` as a second constraint. + +=== "Virtual machine" + + Place the VMs in different availability zones, or at minimum on different hypervisor hosts. Two VMs in one zone survive a process crash but not a zone outage. + + Set a restart policy so a crashed container comes back without manual intervention — the shipped compose file already uses `restart: unless-stopped`. Confirm it survived any edits you made. + +## Autoscaling and disruption + +=== "Kubernetes" + + ```yaml + api-portal-ui: + hpa: + enabled: true + minReplicas: 2 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: "" # set a number to also scale on memory + podDisruptionBudget: + enabled: true + minAvailable: 1 + ``` + + The chart is fail-fast: it refuses to render an autoscaler for a deployment whose database cannot be shared, rather than scaling one into corruption. + + Two prerequisites, both easy to miss: + + - **`deployment.resources.requests.cpu` must be set.** Utilization is a ratio against the request; with no request there is nothing to divide by, and the HPA reports unknown metrics indefinitely. + - **metrics-server must be running** in the cluster. + + For the PodDisruptionBudget, set exactly one of `minAvailable` or `maxUnavailable`, and enable it only at two or more replicas. With a single pod, `minAvailable: 1` blocks node drains entirely — the cluster can never evict the only pod, so maintenance stalls. + +=== "Virtual machine" + + There is no autoscaler. Capacity is the number of VMs you run, so size for peak rather than average and add VMs when sustained load approaches it. + + For planned maintenance, take one VM out of the load balancer pool, upgrade it, return it, then move to the next. That is the manual equivalent of a rolling update, and it is why two instances is the floor — with one, there is nothing to drain to. + +Scaling up in either case adds database connections (`instances × max_open_conns`), so bound the ceiling against what the database can absorb. See [Database Configuration](database-configuration.md#connection-pool-tuning). + +## Health checks + +The portal answers `/health` at both the container root and under the `/api-portal` prefix, precisely so a health check can use it. It deliberately bypasses the session middleware, so a check every few seconds doesn't write a session row each time. + +=== "Kubernetes" + + Point both probes at `/health`: + + ```yaml + api-portal-ui: + deployment: + livenessProbe: + httpGet: { path: /health, port: http, scheme: HTTPS } + initialDelaySeconds: 15 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + httpGet: { path: /health, port: http, scheme: HTTPS } + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 3 + ``` + + If you set `tls.certificateProvider: none`, change both probes' `scheme` from `HTTPS` to `HTTP` — otherwise every probe fails and pods never become ready. + +=== "Virtual machine" + + Point the load balancer's health check at `https://<vm>:9543/health` and configure it to remove an instance that fails. Without that, the load balancer keeps sending traffic to a portal that has stopped serving. + + The compose file has no health check for the portal service. Adding one gives Docker the same signal locally: + + ```yaml + services: + api-portal: + healthcheck: + test: ["CMD", "curl", "-fk", "https://localhost:9543/health"] + interval: 30s + timeout: 5s + retries: 3 + ``` + +## Related + +- [Database Configuration](database-configuration.md): the connection budget your instance count spends +- [Deploy & Verify](deploy-and-verify.md): confirming instances came up healthy +- [Security Hardening](security-hardening.md): the upload ceilings that inform memory sizing diff --git a/en/docs/api-portal/1.0.0/deployment/security-hardening.md b/en/docs/api-portal/1.0.0/deployment/security-hardening.md new file mode 100644 index 000000000..cf01e93c6 --- /dev/null +++ b/en/docs/api-portal/1.0.0/deployment/security-hardening.md @@ -0,0 +1,214 @@ +--- +title: "Security Hardening" +description: "Harden an API Portal & MCP Hub production deployment: shared secrets, TLS certificates, identity-provider authentication, scope authorization, and the try-it proxy and upload ceilings." +canonical_url: https://wso2.com/api-platform/docs/api-portal/deployment/security-hardening/ +md_url: https://wso2.com/api-platform/docs/api-portal/deployment/security-hardening.md +tags: + - cloud + - api-portal + - deployment + - security +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Security Hardening + +Everything on this page applies to a single-instance production deployment as much as to a replicated one. The parts specific to running more than one pod are called out where they arise. + +## Secrets + +Nothing generates secrets at startup. They are provisioned once, before first boot, and delivered to every instance: + +=== "Kubernetes" + + The chart never creates or embeds secret values. Provision them with the bundled `generate-secrets.sh`, then point the release at the Secret it wrote: + + ```yaml + api-portal-ui: + secrets: + existingSecret: my-release-api-portal-ui-secrets + ``` + + Rendering **fails** if `existingSecret` is unset, so there is no path to starting a release without them. + +=== "Virtual machine" + + `scripts/setup.sh` writes them as files under `resources/`, which the compose file mounts into the container. They are read through {% raw %}`{{ file }}`{% endraw %} tokens rather than environment variables deliberately, so they never appear in `docker inspect` or a process environment dump: + + | File | Purpose | + |---|---| + | `resources/keys/api-portal-encryption.key` | At-rest encryption key | + | `resources/keys/api-portal-session-secret` | Session-signing secret | + | `resources/certificates/cert.pem` + `key.pem` | TLS pair | + + Run the script on one VM and copy `resources/keys/` and `resources/certificates/` to the others. Running it separately per VM generates different values on each — see the warning below. + +The values themselves are the same on either substrate: + +| Secret key / env var | Required | Purpose | +|---|---|---| +| `APIP_AP_SECURITY_ENCRYPTION_KEY` | Always | Encrypts stored credentials at rest | +| `APIP_AP_SECURITY_SESSION_SECRET` | Always | Signs session cookies | +| `APIP_AP_DATABASE_PASSWORD` | External database | Database login | +| `APIP_AP_AUTH_IDP_CLIENT_SECRET` | `idp` mode | OIDC client secret | +| `jwt_public.pem` | Local auth only | Mounted as a **file**, not an env var — the Platform API's RS256 public key. Not needed in production, where login goes to your identity provider. | + +On Kubernetes the optional keys are wired only when the matching flag is true — `secrets.hasIdpClientSecret`, `secrets.hasPublicKey` — and `generate-secrets.sh` sets these in the values file it writes. + +!!! warning "Two secrets must be byte-identical on every instance" + A mismatched `APIP_AP_SECURITY_SESSION_SECRET` means each instance rejects cookies signed by the others, so users are logged out at random as the load balancer moves them around. A mismatched `APIP_AP_SECURITY_ENCRYPTION_KEY` means credentials written by one instance can't be decrypted by another — intermittent failures that read as data corruption. + + On Kubernetes, referencing one Secret from the release guarantees it. On VMs it is on you: generate once, then copy `resources/keys/` to every host. Both provisioning scripts leave existing material untouched when re-run, so upgrades don't rotate secrets out from under running instances. + +Rotating the encryption key requires re-encrypting stored credentials. Treat it as a planned migration, not a routine rotation. + +## TLS configuration + +The portal image does **not** generate a certificate for itself. Supply a real one, or terminate TLS in front of it. + +=== "Kubernetes" + + ```yaml + api-portal-ui: + tls: + certificateProvider: cert-manager # cert-manager | secret | none + mountPath: /app/certs + ``` + + | Provider | Use when | + |---|---| + | `cert-manager` | cert-manager runs in the cluster. Set `tls.certManager.issuerRef`, `commonName`, and `dnsNames` to your real hostname — the shipped defaults point at `devportal.localhost` and are development values. | + | `secret` | You hold a certificate already. Set `tls.secret.name` and, if your keys differ, `certKey` / `keyKey`. | + | `none` | A TLS-terminating proxy or ingress fronts the portal. The listener then serves plain HTTP. | + + `selfSigned` is deliberately **not** supported — the image has no in-container certificate generation, so the chart rejects it at render time rather than starting a pod that can't serve HTTPS. + +=== "Virtual machine" + + `setup.sh` writes a **self-signed** pair to `resources/certificates/`, which the compose file mounts at `/etc/api-portal/tls`. That is fine for evaluation and wrong for production — browsers warn, and any client verifying the chain refuses. + + Replace both files with a certificate for your real hostname, keeping the same filenames, then recreate the container: + + ``` + resources/certificates/cert.pem + resources/certificates/key.pem + ``` + + If a reverse proxy on the VM terminates TLS instead, set `APIP_AP_SERVER_HTTPS_ENABLED=false` in `api-platform.env` and bind the portal to loopback only. + +Terminating TLS in front of the portal is a legitimate production choice, but the hop from the terminator to the portal is then cleartext. Encrypt that hop too wherever it crosses a host or network boundary. + +## Authentication + +Local authentication posts credentials to a Platform API, which validates them against a file-based user list. It exists for development and demos, and it is the portal's only outbound call to a Platform API — production deployments delegate login to an OIDC identity provider and make no such call at all, so accounts, password policy, and revocation live in your identity system: + +=== "Kubernetes" + + ```yaml + api-portal-ui: + config: + auth: + idp: + issuer: https://idp.example.com/oauth2/token + authorizationUrl: https://idp.example.com/oauth2/authorize + tokenUrl: https://idp.example.com/oauth2/token + userInfoUrl: https://idp.example.com/oauth2/userinfo + jwksUrl: https://idp.example.com/oauth2/jwks + clientId: <portal-client-id> + callbackUrl: https://portal.example.com/api-portal/<org-handle>/callback + logoutUrl: https://idp.example.com/oidc/logout + scope: "openid profile email" + ``` + + The chart renders `auth.mode=idp` once `idp.clientId` is set; the client secret comes from the Secret. + +=== "Virtual machine" + + In `configs/config.toml`: + + ```toml + [api_portal.auth] + mode = "idp" + + [api_portal.auth.idp] + issuer = "https://idp.example.com/oauth2/token" + authorization_url = "https://idp.example.com/oauth2/authorize" + token_url = "https://idp.example.com/oauth2/token" + user_info_url = "https://idp.example.com/oauth2/userinfo" + jwks_url = "https://idp.example.com/oauth2/jwks" + client_id = "<portal-client-id>" + callback_url = "https://portal.example.com/api-portal/<org-handle>/callback" + logout_url = "https://idp.example.com/oidc/logout" + scope = "openid profile email" + ``` + + Keep the client secret out of this file — reference it from a mounted file instead, and put the file on every VM: + + {% raw %} + + ```toml + client_secret = '{{ file "/secrets/api-portal/oidc_client_secret" }}' + ``` + + {% endraw %} + +Both the callback and logout redirect URLs must sit under the portal's `/api-portal` mount and match what you registered in the identity provider. + +The OIDC endpoints have no defaults on purpose, and the portal refuses to start in `idp` mode without `issuer`, `authorizationUrl`, `tokenUrl`, `clientId`, and `callbackUrl`. See [Connect an identity provider](../setting-up/authentication/connect-an-identity-provider.md). + +## Authorization + +Authorization is separate from authentication and applies in both modes. Keep it on: + +```yaml +api-portal-ui: + config: + auth: + authorization: + enabled: true + mode: role +``` + +`enabled: false` makes any authenticated caller satisfy every Management API operation's scope list. It is a development opt-out and logs a startup warning. + +In the default `role` mode the portal expands the token's roles claim through a grant table and ignores the token's own scope claim, so a caller can't widen a role's grant by requesting extra scopes. Mount your grant table rather than baking it into an image, so changing what a role may do is a restart rather than a rebuild. See [Authentication](../setting-up/authentication/overview.md). + +## Try-it proxy + +The try-it console calls an API's registered endpoint server-side, through a same-origin proxy, so every gateway doesn't need CORS headers naming the portal. Two of its defaults are development-oriented and should be tightened in production: + +```toml +[api_portal.tryout] +allow_http_endpoints = false # default true — production should allow only https:// +allow_private_endpoints = false # default; leave false unless the gateway is genuinely private +tls_skip_verify = false # development only +timeout_ms = 15000 +max_request_bytes = 1048576 +max_response_bytes = 5242880 +``` + +`allow_private_endpoints` is deny-by-default because the registered-endpoint allowlist can't protect against an endpoint registered to point at an internal service. Set it `true` only when the gateway legitimately sits on a private address — a cluster IP or in-cluster service name — and only after confirming which services the portal can reach. + +Two protections hold regardless of these settings: the proxy only calls URLs contained by an endpoint registered for that API, so a caller can't choose an arbitrary host; and link-local and cloud-metadata addresses such as `169.254.169.254` are refused at connection time. + +## Upload ceilings + +Uploads and archive extraction — theme ZIPs, API specifications, documents, landing-page content — are bounded by built-in defaults: + +```toml +[api_portal.uploads] +max_bytes = 10485760 # 10 MiB per upload, or per entry inside an archive +max_total_bytes = 52428800 # 50 MiB total extracted size per archive +max_zip_entries = 500 +max_depth = 10 +``` + +`max_total_bytes`, `max_zip_entries`, and `max_depth` are what guard archive extraction against a decompression bomb. Raise them only deliberately, and only as far as a legitimate artifact needs. + +## Related + +- [Database Configuration](database-configuration.md): TLS to the database and the password Secret +- [Control Plane Connection](control-plane-connection.md): securing the portal-to-Platform-API hop +- [Configurations](../references/configurations.md): every key referenced here diff --git a/en/docs/api-portal/next/deployment/control-plane-connection.md b/en/docs/api-portal/next/deployment/control-plane-connection.md new file mode 100644 index 000000000..55cb032ef --- /dev/null +++ b/en/docs/api-portal/next/deployment/control-plane-connection.md @@ -0,0 +1,100 @@ +--- +title: "Control Plane Connection" +description: "How a standalone API Portal & MCP Hub deployment reaches gateways: registering a webhook subscriber against an existing Platform API, and the local-auth connection it does not need in production." +canonical_url: https://wso2.com/api-platform/docs/api-portal/deployment/control-plane-connection/ +md_url: https://wso2.com/api-platform/docs/api-portal/deployment/control-plane-connection.md +tags: + - cloud + - api-portal + - deployment + - platform-api +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Control Plane Connection + +The API Portal & MCP Hub is a standalone product. It has no runtime dependency on a control plane: it stores its own catalog, authenticates against your identity provider, and reaches gateways by emitting signed events to an endpoint you register. + +There are exactly two places a Platform API can enter the picture, and in a production deployment only the second one usually applies: + +| Use | Direction | Needed in production? | +|---|---|---| +| Local authentication | Portal → Platform API | **No.** Production delegates login to an identity provider. | +| Webhook subscriber | Portal → subscriber | Only if you want credentials propagated to gateways. | + +## Credential propagation + +The portal doesn't hold gateway-specific logic and has no fixed binding to a control plane. When a credential or plan changes, it emits a signed event to every subscriber registered for that organization. Whoever receives it decides what to do with it. + +If you already run a Platform API fronting your gateways, that is the natural subscriber: it verifies the signature, decrypts the credential, persists it, and pushes it out to every gateway serving the API. That Platform API belongs to your existing estate — you point the portal at it, rather than deploying one alongside the portal. + +A gateway that consumes the events itself can subscribe directly instead, as can a handler of your own. And if nothing subscribes, the portal still works: credentials are created and shown to developers, they just aren't propagated anywhere. + +!!! warning "No subscriber means silent non-propagation" + With no subscriber registered, credentials never reach a gateway and nothing reports an error — from the portal's point of view there was simply no one to notify. If developers report that a freshly generated key is rejected at the gateway, check the subscriber registration first. + +### Register the subscriber + +Registration is runtime state, held per organization in the portal's database — not chart configuration. Do it once after the first deployment, through **Settings → Webhooks** or the Management API. It survives upgrades, and it's per organization rather than per pod, so the replica count doesn't change anything. + +Two things have to agree: + +- **`targetUrl`** must be the receiver's webhook endpoint, reachable from the portal's network. For a Platform API, that is `/api/internal/v0.9/webhook/events` on its host. +- **`secret`** must equal the receiver's configured webhook secret — `APIP_CP_WEBHOOK_SECRET` on a Platform API. It does double duty, signing each delivery and deriving the key that encrypts credential fields, so a mismatch fails both signature verification and decryption. + +On a Platform API, the webhook receiver ships **disabled**. Enable it and give it the same secret, or deliveries are rejected at the far end while the portal records them as sent. See [Webhook integration](../admin-settings/webhook-integration.md). + +### Reaching a subscriber outside the cluster + +An existing Platform API is usually not in the portal's namespace, so `targetUrl` is whatever address is routable from the portal's pods — an in-cluster Service name if it happens to be co-located, otherwise its external hostname. Make sure any egress NetworkPolicy allows it; a blocked delivery retries and eventually lands in `FAILED` rather than failing loudly at registration time. + +### In a replicated deployment + +Every portal pod runs its own webhook dispatcher and delivery worker, all claiming from the same queue. The claim locks are what keep two pods from dispatching the same event — see [Overview](overview.md#what-makes-a-replica-interchangeable). + +A delivery whose pod dies mid-flight is not lost: one left `IN_FLIGHT` for more than five minutes is marked `FAILED` and re-enters the queue on the next cycle. That recovery sweep is scoped to the organization, so pods serving other organizations never reset each other's genuinely in-flight work. + +## Local authentication (development only) + +This is the portal's one and only outbound call to a Platform API, and production deployments don't make it. The built-in login form posts credentials to the Platform API's portal login endpoint and verifies the returned RS256 token against that component's public key: + +=== "Kubernetes" + + ```yaml + api-portal-ui: + config: + platformApi: + baseUrl: "" # "" derives the in-cluster URL + insecure: false + auth: + publicKeyPath: /etc/devportal/keys/jwt_public.pem + secrets: + hasPublicKey: true + ``` + + `baseUrl` must use the in-cluster Service name and **container** port, because the portal connects over the cluster network rather than through a published port or ingress. Pointing it at an externally published address makes every login fail while the Platform API itself looks healthy. + + `generate-secrets.sh` copies the public key out of the Platform API's own Secret, so the two stay on the same keypair. Set `hasPublicKey: false` when you have no local-auth path at all. + +=== "Virtual machine" + + ```toml + [api_portal.auth.local] + platform_api_url = "https://platform-api:9243" + public_key_path = "/etc/api-portal/keys/jwt_public.pem" + tls_skip_verify = false + ``` + + When the Platform API runs in the same Compose stack, use its service name and container port — the portal reaches it over the Compose network, not through a published host port. `setup.sh` writes the keypair to `resources/keys/`, and the compose file mounts the public half into the portal. + +Leave `platform_api_url` empty and local authentication is disabled outright. + +Two details matter if you do run it. Skipping TLS verification on that hop (`insecure` / `tls_skip_verify`) exists for local development against a self-signed certificate and belongs nowhere near production. And the public key is what makes the portal verify the token rather than trust the transport — if it's missing, bearer-token requests fail closed rather than being accepted unverified. In `idp` mode tokens are verified against your identity provider's JWKS endpoint instead, and the key goes unused. + +## Related + +- [Webhook integration](../admin-settings/webhook-integration.md): registering and verifying subscribers +- [Authentication](../setting-up/authentication/overview.md): why production uses an identity provider +- [Security Hardening](security-hardening.md): the identity-provider configuration that replaces local auth diff --git a/en/docs/api-portal/next/deployment/database-configuration.md b/en/docs/api-portal/next/deployment/database-configuration.md new file mode 100644 index 000000000..9aa914185 --- /dev/null +++ b/en/docs/api-portal/next/deployment/database-configuration.md @@ -0,0 +1,135 @@ +--- +title: "Database Configuration" +description: "Provision the database for a production API Portal & MCP Hub deployment: schema, password Secret, Helm values, TLS verification, and connection-pool sizing across replicas." +canonical_url: https://wso2.com/api-platform/docs/api-portal/deployment/database-configuration/ +md_url: https://wso2.com/api-platform/docs/api-portal/deployment/database-configuration.md +tags: + - cloud + - api-portal + - deployment + - database +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Database Configuration + +The database is what makes a replicated deployment possible: it holds the catalog, applications, subscriptions, credentials, **user sessions**, and the webhook event queue. Every portal pod reads and writes the same one, which is why no pod needs sticky sessions or a shared filesystem. + +This page covers the production-specific setup. For driver choice and the schema itself, see [Set up the database](../setting-up/database.md). + +Use PostgreSQL or SQL Server. The examples below show PostgreSQL; [Set up the database](../setting-up/database.md) has the equivalents for SQL Server, including the driver values and port. + +## Create the database + +Provision an external, highly available server the cluster can reach, and create a database and a login role for the portal: + +```sql +CREATE DATABASE api_portal; +CREATE USER api_portal WITH PASSWORD '<strong-password>'; +GRANT ALL PRIVILEGES ON DATABASE api_portal TO api_portal; +``` + +The portal owns this database outright. If another component of your estate has its own store, keep them separate — the portal never reads or writes anyone else's. + +## Apply the schema + +Apply the schema before first start: + +```bash +psql -h postgres.example.com -U api_portal -d api_portal -f database/schema.postgres.sql +``` + +A reference copy ships in the distribution under `resources/api-portal/db-scripts/`, and the same file is bundled inside the image. See [Apply the schema](../setting-up/database.md#apply-the-schema-for-postgresql-or-microsoft-sql-server). + +## Store the password in a Secret + +The password never appears in a values file. It comes from the release's Secret, under the key `APIP_AP_DATABASE_PASSWORD` — the same name as the environment variable the container receives. `generate-secrets.sh` prompts for it and writes it into the Secret it creates. See [Security Hardening → Secrets](security-hardening.md#secrets). + +## Configure the chart + +=== "Kubernetes" + + ```yaml + api-portal-ui: + config: + database: + type: postgres + host: postgres.example.com + port: 5432 + database: api_portal # the schema name — rendered into config as `name` + user: api_portal + sslmode: verify-full # disable | require | verify-ca | verify-full + sslRootCert: /etc/api-portal/certs/ca.pem + ``` + + Two key names catch people out: the schema name is `database`, not `name`, and `sslmode` is all lowercase. + + Switching `type` off `sqlite` is also what unlocks scaling — the chart refuses to render an autoscaler on a database that can't be shared. + +=== "Virtual machine" + + The shipped `configs/config.toml` carries only `driver` and `path`, so **setting `APIP_AP_DATABASE_HOST` on its own does nothing** — an environment value reaches a setting only where a token references it. Replace the whole `[api_portal.database]` block with the form for your driver from `configs/config-template.toml`: + + {% raw %} + + ```toml + [api_portal.database] + driver = '{{ env "APIP_AP_DATABASE_DRIVER" "postgres" }}' + host = '{{ env "APIP_AP_DATABASE_HOST" "localhost" }}' + port = '{{ env "APIP_AP_DATABASE_PORT" "5432" }}' + name = '{{ env "APIP_AP_DATABASE_NAME" "api_portal" }}' + user = '{{ env "APIP_AP_DATABASE_USER" "" }}' + password = '{{ env "APIP_AP_DATABASE_PASSWORD" "" }}' + ssl_mode = "verify-full" + ssl_root_cert = "/etc/api-portal/tls/ca.pem" + ``` + + {% endraw %} + + Then set the values in `api-platform.env` on each VM. Note the TOML keys are snake_case (`ssl_mode`, `ssl_root_cert`) where the Helm values are not. + + `user` has no fallback on purpose: a non-SQLite driver with no user, host, or name refuses to start rather than silently connecting as a conventional superuser name to whatever server the defaults point at. + +## TLS to the database + +Use `verify-full` in production. It is the only mode that validates both the certificate chain and the hostname, so it defends against a redirected connection rather than merely encrypting one: + +| Mode | What it checks | +|---|---| +| `disable` | Nothing — cleartext | +| `require` | Encryption only; accepts any certificate | +| `verify-ca` | Certificate chains to a trusted CA | +| `verify-full` | Chain **and** that the hostname matches the certificate | + +`verify-ca` and `verify-full` need `sslRootCert` pointing at the CA certificate, mounted into the pod. + +## Connection pool tuning + +Each pod opens its own pool, so the cluster's total connection demand is: + +``` +replicas × maxOpenConns +``` + +With the default `maxOpenConns: 50`, three replicas ask for up to 150 connections. Keep the total comfortably below the server's `max_connections`, leaving headroom for administrative sessions and for a rolling upgrade — during which old and new pods briefly overlap. + +```yaml +api-portal-ui: + config: + database: + maxOpenConns: 25 + minOpenConns: 2 + poolIdleTimeoutMs: 10000 + poolConnectionTimeoutMs: 30000 + poolRequestTimeoutMs: 30000 +``` + +Lowering `maxOpenConns` as you raise the replica count is usually the right trade: more pods each holding a smaller pool. `minOpenConns` keeps a floor of warm connections per pod, so the first request after an idle period doesn't pay connection setup. + +## Related + +- [Set up the database](../setting-up/database.md): driver choice, the schema, and single-instance setup +- [Resources & Scaling](resources-and-scaling.md): choosing a replica count to size the pool against +- [Security Hardening](security-hardening.md): where the password lives diff --git a/en/docs/api-portal/next/deployment/deploy-and-verify.md b/en/docs/api-portal/next/deployment/deploy-and-verify.md new file mode 100644 index 000000000..f947bda13 --- /dev/null +++ b/en/docs/api-portal/next/deployment/deploy-and-verify.md @@ -0,0 +1,173 @@ +--- +title: "Deploy & Verify" +description: "Install the API Portal & MCP Hub on Kubernetes or virtual machines, confirm instances came up healthy and share sessions, and follow a safe upgrade procedure." +canonical_url: https://wso2.com/api-platform/docs/api-portal/deployment/deploy-and-verify/ +md_url: https://wso2.com/api-platform/docs/api-portal/deployment/deploy-and-verify.md +tags: + - cloud + - api-portal + - deployment + - kubernetes +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Deploy & Verify + +Work through [Security Hardening](security-hardening.md), [Database Configuration](database-configuration.md), and [Resources & Scaling](resources-and-scaling.md) first — the schema applied, the secrets provisioned, and the configuration assembled. This page installs the result and confirms it. + +## Provision the secrets + +=== "Kubernetes" + + The chart refuses to render without them, so this comes first: + + ```bash + ./generate-secrets.sh api-portal my-release # <namespace> [release-name] + ``` + + It writes `values-secrets.yaml`, naming each Secret it created along with the `has*` flags for whichever optional keys it provisioned. Keep that file alongside your own values and pass both at install time. + +=== "Virtual machine" + + Run the setup script once, on the **first** VM only: + + ```bash + ./scripts/setup.sh + ``` + + It provisions the TLS pair under `resources/certificates/` and the portal's encryption key and session secret under `resources/keys/`, and writes `.env` and `api-platform.env`. + + Then copy the generated material to every other VM before starting them, so all instances share it: + + ```bash + rsync -a resources/keys/ resources/certificates/ <other-vm>:<install-dir>/resources/ + ``` + + Do **not** run `setup.sh` independently on each VM — it would generate a different key and session secret per host, which is the failure described in [Security Hardening](security-hardening.md#secrets). + +## Deploy + +=== "Kubernetes" + + The `api-portal` chart pulls its components as OCI dependencies, so fetch them before installing: + + ```bash + helm dependency update ./api-portal-helm-chart + ``` + + Then install with your production values last, so they win: + + ```bash + helm install my-release ./api-portal-helm-chart \ + --namespace api-portal --create-namespace \ + -f values-secrets.yaml \ + -f values-production.yaml + ``` + + Render before you install if you want to see what a values change actually produces — this is also where the chart's fail-fast guards fire, before anything is created: + + ```bash + helm template my-release ./api-portal-helm-chart \ + -f values-secrets.yaml -f values-production.yaml > /dev/null + ``` + + A missing `secrets.existingSecret` or `tls.certificateProvider: selfSigned` fails here with an explicit message. + +=== "Virtual machine" + + On each VM, from the distribution directory: + + ```bash + docker compose up -d + ``` + + `COMPOSE_PROFILES` in `.env` decides what starts. For a standalone portal against an external database, that is `api-portal` alone. + + Check the configuration was read as you intended before putting the VM into the load balancer pool — the startup log reports the resolved database driver and the authorization mode: + + ```bash + docker compose logs api-portal | head -40 + ``` + +## Verify + +Confirm every instance is serving, not just that the deploy command succeeded. + +=== "Kubernetes" + + ```bash + kubectl -n api-portal get pods -l app.kubernetes.io/name=api-portal-ui + kubectl -n api-portal get hpa,pdb + ``` + + Check the pods landed on different nodes — a topology-spread constraint that couldn't be satisfied leaves a pod Pending: + + ```bash + kubectl -n api-portal get pods -o wide -l app.kubernetes.io/name=api-portal-ui + ``` + + Confirm the portal is serving, from inside the cluster: + + ```bash + kubectl -n api-portal exec deploy/my-release-api-portal-ui -- \ + curl -sk https://localhost:9543/health + ``` + +=== "Virtual machine" + + On each VM: + + ```bash + docker compose ps + curl -sk https://localhost:9543/health + ``` + + Then confirm the load balancer sees every instance as healthy, and that its health check is pointed at `/health` rather than `/`. + +Then verify the two things a single-instance smoke test cannot catch, on either substrate: + +**Sessions are shared.** Log in through the load balancer or ingress, then stop the instance that served the login. The next request should land on another instance with the session intact. If you are logged out instead, the instances are not reading the same session table — check that every instance points at the same external database and resolves the same encryption key and session secret. + +**Only one instance dispatches each event.** Trigger a credential change and confirm the registered subscriber receives exactly one delivery, not one per instance. See [Control Plane Connection](control-plane-connection.md#in-a-replicated-deployment). + +## Upgrade + +=== "Kubernetes" + + ```bash + helm dependency update ./api-portal-helm-chart + helm diff upgrade my-release ./api-portal-helm-chart \ + -f values-secrets.yaml -f values-production.yaml # requires the helm-diff plugin + helm upgrade my-release ./api-portal-helm-chart \ + -f values-secrets.yaml -f values-production.yaml + ``` + + **Always pass the secrets values file.** Omitting it on an upgrade fails the render rather than silently dropping the Secret reference — intended behavior, but it does mean an upgrade command is no shorter than an install command. + + Roll back a bad release with `helm rollback my-release`. + +=== "Virtual machine" + + Upgrade one VM at a time so the others keep serving: + + 1. Remove the VM from the load balancer pool. + 2. Update the image tag in `docker-compose.yaml`, then `docker compose up -d` to recreate the container. + 3. Confirm `https://localhost:9543/health` responds, and check the startup log. + 4. Return it to the pool, then move to the next VM. + + Re-running `setup.sh` is safe — it fills in only what is missing and never overwrites an existing value — so an upgrade won't rotate the encryption key out from under stored credentials. + + Roll back by putting the previous image tag back and recreating the container. + +Two things hold on both substrates: + +- **Old and new instances overlap during a rolling upgrade**, so peak database connections briefly exceed `instances × max_open_conns`. Leave headroom for it. +- **A rollback reverts configuration, not database schema changes.** Check the release notes before upgrading across a version that migrates the schema. + +## Related + +- [Security Hardening](security-hardening.md): what the secrets provisioning covers +- [Resources & Scaling](resources-and-scaling.md): the instance count and health checks installed here +- [Control Plane Connection](control-plane-connection.md): the post-install webhook registration diff --git a/en/docs/api-portal/next/deployment/overview.md b/en/docs/api-portal/next/deployment/overview.md new file mode 100644 index 000000000..984e9ae3d --- /dev/null +++ b/en/docs/api-portal/next/deployment/overview.md @@ -0,0 +1,162 @@ +--- +title: "Production deployment" +description: "Deploy the API Portal & MCP Hub in production on Kubernetes with Helm or on virtual machines, with an external database and, where you need it, replicated instances." +canonical_url: https://wso2.com/api-platform/docs/api-portal/deployment/overview/ +md_url: https://wso2.com/api-platform/docs/api-portal/deployment/overview.md +tags: + - cloud + - api-portal + - deployment + - kubernetes + - devops +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Production deployment + +This section covers running the API Portal & MCP Hub in production — on Kubernetes with Helm, or on virtual machines with the Compose distribution. Most of it applies whether you run one instance or several: the hardening, the database setup, and the gateway integration are the same either way. High availability is the one dimension that adds requirements, and they are set out below. + +The portal is a standalone product. A production deployment needs the portal and a database — nothing else. It authenticates users against your identity provider and reaches gateways by emitting signed webhook events to whatever endpoint you register, so it carries no runtime dependency on a control plane. + +Every page here carries **Kubernetes** and **Virtual machine** tabs wherever the mechanics differ. The requirements and failure modes are the same on both; only the delivery changes. + +The single-instance walkthrough in [Getting started](../getting-started.md) is the right shape for evaluation and development, not for production. + +## Running more than one instance + +Replication is what turns a production deployment into a highly available one. What it requires depends on your database. + +A production deployment runs against an external database — PostgreSQL or SQL Server. Both support replication, so either works whether you run one instance or several. + +!!! note "Change the shipped default" + The distribution ships pointing at SQLite so the quickstart works with no external dependency. That is an evaluation default; move to PostgreSQL or SQL Server before you deploy. See [Database Configuration](database-configuration.md). + +A single production instance is a legitimate deployment — it just has no redundancy, so every restart, upgrade, and host failure is an outage. Everything in this section except [Resources & Scaling](resources-and-scaling.md) applies to it unchanged. + +## Prerequisites + +=== "Kubernetes" + + | Tool | Requirement | + |------|-------------| + | `kubectl` | Configured against your target cluster | + | `helm` | Version 3+ | + | `openssl` | Available in your local shell, to generate keys | + | Database | An external, highly available PostgreSQL or SQL Server the cluster can reach | + + ```bash + kubectl cluster-info + kubectl get nodes + helm version + ``` + + The `api-portal` chart is the product package; values for the portal itself nest under the `api-portal-ui` key. It also carries the Platform API as an optional dependency, on by default for the local-auth quickstart. A deployment that authenticates against an identity provider doesn't need it: + + ```yaml + platform-api: + enabled: false + ``` + +=== "Virtual machine" + + | Requirement | Detail | + |---|---| + | Two or more VMs | Each running Docker with the Compose plugin | + | Load balancer | Fronting the VMs, health-checking `/health` | + | `openssl` | Available on the VM you provision secrets from | + | Database | An external, highly available PostgreSQL or SQL Server every VM can reach | + + Each VM gets its own copy of the distribution. The bundled Platform API and the SQLite default are both switched off for a production deployment — see [Resources & Scaling](resources-and-scaling.md#instance-count) and [Database Configuration](database-configuration.md). + +## Architecture + +A production deployment consists of two components: + +| Component | Description | +|---|---| +| **API Portal & MCP Hub** | Serves the catalog, the admin UI, and the Management API. Stateless apart from what it writes to the database, so it replicates freely once that database is shared. | +| **Database** | The shared source of truth: catalog artifacts, applications, subscriptions, credentials, user sessions, and the webhook event queue. | + +Two things sit outside the deployment and are integrations rather than components: your **identity provider**, which the portal redirects to for login, and a **webhook subscriber**, which receives the signed credential events the portal emits. Most estates point the subscriber at an existing Platform API that already fronts their gateways — but that Platform API belongs to that estate, not to this deployment, and the portal neither installs nor manages it. See [Control Plane Connection](control-plane-connection.md). + +Portal replicas hold no state of their own. Every request can land on any pod, which is what makes a plain round-robin Service sufficient — no sticky sessions, no shared filesystem. + +### What makes a replica interchangeable + +Two things, each worth knowing because each one breaks in a recognizable way if you get the configuration wrong: + +**Sessions live in the database, not in memory.** The portal writes sessions through a SQL session store shared by every dialect, so a user who logs in against one instance stays logged in when the next request lands on another. + +**Webhook dispatch is claimed with row locks.** Each replica runs its own webhook dispatcher and delivery worker. They claim work with `SELECT ... FOR UPDATE SKIP LOCKED`, scoped to the organization, so two replicas never dispatch the same event. Claims that die mid-flight are recovered: a delivery stuck `IN_FLIGHT` for more than five minutes is marked `FAILED` and re-enters the queue on the next cycle. + +## Topology + +Run at least two instances, in different failure domains — separate worker nodes on Kubernetes, separate availability zones or hypervisor hosts on VMs. Two instances sharing a domain survive a process crash but not the loss of that domain. See [Resources & Scaling](resources-and-scaling.md#spread-across-failure-domains). + +## Fronting the portal + +Nothing in the deployment provisions a public entry point — bring your own ingress controller or load balancer. Two portal-specific details matter, on either substrate: + +- **The portal serves under the `/api-portal` path prefix.** The origin root only redirects there. Route `/api-portal/*` through, and don't strip the prefix — the session cookie is scoped to that exact path, and a mismatch is a silent login loop rather than a visible error. +- **Set the public base URL.** It is what the portal embeds in generated AI-agent prompts. Behind a proxy it can't be inferred from the request, and a stale value produces prompts pointing at an unreachable host. + +=== "Kubernetes" + + The chart deploys a `Service`, not an Ingress. + + ```yaml + api-portal-ui: + config: + server: + baseUrl: https://portal.example.com + ``` + +=== "Virtual machine" + + Point the load balancer at port `9543` on each VM, with its health check on `/health`. + + ```toml + [api_portal.server] + base_url = "https://portal.example.com" + ``` + + Set it explicitly whenever the portal sits behind a proxy — it cannot be inferred from the request. See [Change the ports the API Portal uses](../setting-up/ports.md). + +Sessions live in the shared database, so no sticky-session configuration is required in either case. + +For the portal's own HTTPS listener, see [Security Hardening → TLS configuration](security-hardening.md#tls-configuration). + +## Setup steps + +Work through these in order — each builds on the last, and the Kubernetes chart's fail-fast guards assume the earlier ones are done: + +1. **[Security Hardening](security-hardening.md)** — provision the secrets, supply a real TLS certificate, switch authentication to your identity provider, and tighten the try-it proxy. +2. **[Database Configuration](database-configuration.md)** — create the database, apply the schema, and size the connection pool against your instance count. +3. **[Resources & Scaling](resources-and-scaling.md)** — set resources, spread instances across failure domains, and configure health checks. Skip to the next step if you are running a single instance. +4. **[Deploy & Verify](deploy-and-verify.md)** — install, then confirm the deployment is serving. +5. **[Control Plane Connection](control-plane-connection.md)** — register the webhook subscriber so credentials reach your gateways. + +## Production checklist + +Before you take traffic: + +- [ ] The database is an external, highly available PostgreSQL or SQL Server, with full TLS verification on +- [ ] The encryption key and session secret are identical on every instance +- [ ] Connection pool sized for `instances × max_open_conns` +- [ ] No `[api_portal.design_mode]` block in `config.toml` — that is a local authoring mode, not a deployment option +- [ ] Authentication mode is `idp`, with the client secret delivered out of band +- [ ] Authorization is enabled +- [ ] The public base URL matches the real origin, and the proxy preserves `/api-portal` +- [ ] The TLS certificate is a real one, not the self-signed pair from setup +- [ ] Instances spread across failure domains, with a health check on `/health` + +Kubernetes adds two: the PodDisruptionBudget is enabled, and CPU requests are set if the HPA is on. + +## Related + +- [Set up the database](../setting-up/database.md): schema provisioning, TLS, and connection pooling +- [Configuration and environment interpolation](../setting-up/configuration.md): how secrets reach `config.toml` without being written into it +- [Change the ports the API Portal uses](../setting-up/ports.md): moving the stack off its defaults +- [Connect an identity provider](../setting-up/authentication/connect-an-identity-provider.md): the production authentication path diff --git a/en/docs/api-portal/next/deployment/resources-and-scaling.md b/en/docs/api-portal/next/deployment/resources-and-scaling.md new file mode 100644 index 000000000..4fd6eb8f5 --- /dev/null +++ b/en/docs/api-portal/next/deployment/resources-and-scaling.md @@ -0,0 +1,184 @@ +--- +title: "Resources & Scaling" +description: "Size and scale a replicated API Portal & MCP Hub deployment on Kubernetes or virtual machines: instance count, resources, spreading across failure domains, and health checks." +canonical_url: https://wso2.com/api-platform/docs/api-portal/deployment/resources-and-scaling/ +md_url: https://wso2.com/api-platform/docs/api-portal/deployment/resources-and-scaling.md +tags: + - cloud + - api-portal + - deployment + - kubernetes +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Resources & Scaling + +The portal is a Node.js application that renders pages and serves the Management API. It holds no state of its own, so scaling it out is a matter of instance count — once the database is a shared one. + +Two is the practical minimum for availability: one instance means every restart, upgrade, and host failure is an outage. Anything above one requires the external database from [Database Configuration](database-configuration.md). + +## Instance count + +=== "Kubernetes" + + ```yaml + api-portal-ui: + deployment: + replicaCount: 2 + ``` + +=== "Virtual machine" + + Run the portal on two or more VMs, each with its own copy of the distribution, all pointed at the same external database and fronted by a load balancer. + + Drop `platform-api` from the `COMPOSE_PROFILES` line in `.env` on each VM so only the portal runs — an external or already-existing Platform API, if you use one at all, is not part of this stack: + + ``` + COMPOSE_PROFILES=api-portal + ``` + + No sticky sessions are needed on the load balancer: sessions live in the shared database, so any VM can serve any request. Plain round-robin is correct. + +## Resources + +Memory is the resource to watch. Uploads and archive extraction are bounded by the ceilings in [Security Hardening](security-hardening.md#upload-ceilings) — up to 50 MiB of extracted content per archive by default — so an instance handling a theme upload transiently needs more than its steady-state footprint. Size the ceiling above that peak, not against the idle figure. + +=== "Kubernetes" + + Set requests on every production pod. Without a CPU request the scheduler can't place pods sensibly and the HPA can't compute utilization at all: + + ```yaml + api-portal-ui: + deployment: + resources: + requests: + cpu: 500m + memory: 512Mi + limits: + memory: 1Gi + ``` + + Prefer leaving `limits.cpu` unset. A CPU limit throttles rather than kills, and throttling a single-threaded event loop turns a brief burst into queued latency across every request that pod is serving. + +=== "Virtual machine" + + Give each VM at least 2 vCPU and 2 GiB of RAM, then bound the container itself in `docker-compose.yaml` so a runaway upload can't take the host with it: + + ```yaml + services: + api-portal: + mem_limit: 1g + ``` + + Leave CPU unconstrained for the same reason Kubernetes deployments should skip `limits.cpu` — throttling a single-threaded event loop converts a burst into queued latency. + +## Spread across failure domains + +Instances that share a failure domain buy nothing when that domain fails. + +=== "Kubernetes" + + ```yaml + api-portal-ui: + deployment: + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule + labelSelector: + matchLabels: + app.kubernetes.io/name: api-portal-ui + ``` + + `DoNotSchedule` is deliberate: it leaves a pod Pending rather than co-locating it, which surfaces an under-provisioned cluster as a visible unscheduled pod instead of a silently fragile deployment. Use `ScheduleAnyway` only if you would rather have a degraded placement than a Pending pod. + + For spreading across availability zones, add `topology.kubernetes.io/zone` as a second constraint. + +=== "Virtual machine" + + Place the VMs in different availability zones, or at minimum on different hypervisor hosts. Two VMs in one zone survive a process crash but not a zone outage. + + Set a restart policy so a crashed container comes back without manual intervention — the shipped compose file already uses `restart: unless-stopped`. Confirm it survived any edits you made. + +## Autoscaling and disruption + +=== "Kubernetes" + + ```yaml + api-portal-ui: + hpa: + enabled: true + minReplicas: 2 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: "" # set a number to also scale on memory + podDisruptionBudget: + enabled: true + minAvailable: 1 + ``` + + The chart is fail-fast: it refuses to render an autoscaler for a deployment whose database cannot be shared, rather than scaling one into corruption. + + Two prerequisites, both easy to miss: + + - **`deployment.resources.requests.cpu` must be set.** Utilization is a ratio against the request; with no request there is nothing to divide by, and the HPA reports unknown metrics indefinitely. + - **metrics-server must be running** in the cluster. + + For the PodDisruptionBudget, set exactly one of `minAvailable` or `maxUnavailable`, and enable it only at two or more replicas. With a single pod, `minAvailable: 1` blocks node drains entirely — the cluster can never evict the only pod, so maintenance stalls. + +=== "Virtual machine" + + There is no autoscaler. Capacity is the number of VMs you run, so size for peak rather than average and add VMs when sustained load approaches it. + + For planned maintenance, take one VM out of the load balancer pool, upgrade it, return it, then move to the next. That is the manual equivalent of a rolling update, and it is why two instances is the floor — with one, there is nothing to drain to. + +Scaling up in either case adds database connections (`instances × max_open_conns`), so bound the ceiling against what the database can absorb. See [Database Configuration](database-configuration.md#connection-pool-tuning). + +## Health checks + +The portal answers `/health` at both the container root and under the `/api-portal` prefix, precisely so a health check can use it. It deliberately bypasses the session middleware, so a check every few seconds doesn't write a session row each time. + +=== "Kubernetes" + + Point both probes at `/health`: + + ```yaml + api-portal-ui: + deployment: + livenessProbe: + httpGet: { path: /health, port: http, scheme: HTTPS } + initialDelaySeconds: 15 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + httpGet: { path: /health, port: http, scheme: HTTPS } + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 3 + ``` + + If you set `tls.certificateProvider: none`, change both probes' `scheme` from `HTTPS` to `HTTP` — otherwise every probe fails and pods never become ready. + +=== "Virtual machine" + + Point the load balancer's health check at `https://<vm>:9543/health` and configure it to remove an instance that fails. Without that, the load balancer keeps sending traffic to a portal that has stopped serving. + + The compose file has no health check for the portal service. Adding one gives Docker the same signal locally: + + ```yaml + services: + api-portal: + healthcheck: + test: ["CMD", "curl", "-fk", "https://localhost:9543/health"] + interval: 30s + timeout: 5s + retries: 3 + ``` + +## Related + +- [Database Configuration](database-configuration.md): the connection budget your instance count spends +- [Deploy & Verify](deploy-and-verify.md): confirming instances came up healthy +- [Security Hardening](security-hardening.md): the upload ceilings that inform memory sizing diff --git a/en/docs/api-portal/next/deployment/security-hardening.md b/en/docs/api-portal/next/deployment/security-hardening.md new file mode 100644 index 000000000..cf01e93c6 --- /dev/null +++ b/en/docs/api-portal/next/deployment/security-hardening.md @@ -0,0 +1,214 @@ +--- +title: "Security Hardening" +description: "Harden an API Portal & MCP Hub production deployment: shared secrets, TLS certificates, identity-provider authentication, scope authorization, and the try-it proxy and upload ceilings." +canonical_url: https://wso2.com/api-platform/docs/api-portal/deployment/security-hardening/ +md_url: https://wso2.com/api-platform/docs/api-portal/deployment/security-hardening.md +tags: + - cloud + - api-portal + - deployment + - security +author: WSO2 API Platform Documentation Team +last_updated: 2026-08-06 +content_type: "how-to" +--- + +# Security Hardening + +Everything on this page applies to a single-instance production deployment as much as to a replicated one. The parts specific to running more than one pod are called out where they arise. + +## Secrets + +Nothing generates secrets at startup. They are provisioned once, before first boot, and delivered to every instance: + +=== "Kubernetes" + + The chart never creates or embeds secret values. Provision them with the bundled `generate-secrets.sh`, then point the release at the Secret it wrote: + + ```yaml + api-portal-ui: + secrets: + existingSecret: my-release-api-portal-ui-secrets + ``` + + Rendering **fails** if `existingSecret` is unset, so there is no path to starting a release without them. + +=== "Virtual machine" + + `scripts/setup.sh` writes them as files under `resources/`, which the compose file mounts into the container. They are read through {% raw %}`{{ file }}`{% endraw %} tokens rather than environment variables deliberately, so they never appear in `docker inspect` or a process environment dump: + + | File | Purpose | + |---|---| + | `resources/keys/api-portal-encryption.key` | At-rest encryption key | + | `resources/keys/api-portal-session-secret` | Session-signing secret | + | `resources/certificates/cert.pem` + `key.pem` | TLS pair | + + Run the script on one VM and copy `resources/keys/` and `resources/certificates/` to the others. Running it separately per VM generates different values on each — see the warning below. + +The values themselves are the same on either substrate: + +| Secret key / env var | Required | Purpose | +|---|---|---| +| `APIP_AP_SECURITY_ENCRYPTION_KEY` | Always | Encrypts stored credentials at rest | +| `APIP_AP_SECURITY_SESSION_SECRET` | Always | Signs session cookies | +| `APIP_AP_DATABASE_PASSWORD` | External database | Database login | +| `APIP_AP_AUTH_IDP_CLIENT_SECRET` | `idp` mode | OIDC client secret | +| `jwt_public.pem` | Local auth only | Mounted as a **file**, not an env var — the Platform API's RS256 public key. Not needed in production, where login goes to your identity provider. | + +On Kubernetes the optional keys are wired only when the matching flag is true — `secrets.hasIdpClientSecret`, `secrets.hasPublicKey` — and `generate-secrets.sh` sets these in the values file it writes. + +!!! warning "Two secrets must be byte-identical on every instance" + A mismatched `APIP_AP_SECURITY_SESSION_SECRET` means each instance rejects cookies signed by the others, so users are logged out at random as the load balancer moves them around. A mismatched `APIP_AP_SECURITY_ENCRYPTION_KEY` means credentials written by one instance can't be decrypted by another — intermittent failures that read as data corruption. + + On Kubernetes, referencing one Secret from the release guarantees it. On VMs it is on you: generate once, then copy `resources/keys/` to every host. Both provisioning scripts leave existing material untouched when re-run, so upgrades don't rotate secrets out from under running instances. + +Rotating the encryption key requires re-encrypting stored credentials. Treat it as a planned migration, not a routine rotation. + +## TLS configuration + +The portal image does **not** generate a certificate for itself. Supply a real one, or terminate TLS in front of it. + +=== "Kubernetes" + + ```yaml + api-portal-ui: + tls: + certificateProvider: cert-manager # cert-manager | secret | none + mountPath: /app/certs + ``` + + | Provider | Use when | + |---|---| + | `cert-manager` | cert-manager runs in the cluster. Set `tls.certManager.issuerRef`, `commonName`, and `dnsNames` to your real hostname — the shipped defaults point at `devportal.localhost` and are development values. | + | `secret` | You hold a certificate already. Set `tls.secret.name` and, if your keys differ, `certKey` / `keyKey`. | + | `none` | A TLS-terminating proxy or ingress fronts the portal. The listener then serves plain HTTP. | + + `selfSigned` is deliberately **not** supported — the image has no in-container certificate generation, so the chart rejects it at render time rather than starting a pod that can't serve HTTPS. + +=== "Virtual machine" + + `setup.sh` writes a **self-signed** pair to `resources/certificates/`, which the compose file mounts at `/etc/api-portal/tls`. That is fine for evaluation and wrong for production — browsers warn, and any client verifying the chain refuses. + + Replace both files with a certificate for your real hostname, keeping the same filenames, then recreate the container: + + ``` + resources/certificates/cert.pem + resources/certificates/key.pem + ``` + + If a reverse proxy on the VM terminates TLS instead, set `APIP_AP_SERVER_HTTPS_ENABLED=false` in `api-platform.env` and bind the portal to loopback only. + +Terminating TLS in front of the portal is a legitimate production choice, but the hop from the terminator to the portal is then cleartext. Encrypt that hop too wherever it crosses a host or network boundary. + +## Authentication + +Local authentication posts credentials to a Platform API, which validates them against a file-based user list. It exists for development and demos, and it is the portal's only outbound call to a Platform API — production deployments delegate login to an OIDC identity provider and make no such call at all, so accounts, password policy, and revocation live in your identity system: + +=== "Kubernetes" + + ```yaml + api-portal-ui: + config: + auth: + idp: + issuer: https://idp.example.com/oauth2/token + authorizationUrl: https://idp.example.com/oauth2/authorize + tokenUrl: https://idp.example.com/oauth2/token + userInfoUrl: https://idp.example.com/oauth2/userinfo + jwksUrl: https://idp.example.com/oauth2/jwks + clientId: <portal-client-id> + callbackUrl: https://portal.example.com/api-portal/<org-handle>/callback + logoutUrl: https://idp.example.com/oidc/logout + scope: "openid profile email" + ``` + + The chart renders `auth.mode=idp` once `idp.clientId` is set; the client secret comes from the Secret. + +=== "Virtual machine" + + In `configs/config.toml`: + + ```toml + [api_portal.auth] + mode = "idp" + + [api_portal.auth.idp] + issuer = "https://idp.example.com/oauth2/token" + authorization_url = "https://idp.example.com/oauth2/authorize" + token_url = "https://idp.example.com/oauth2/token" + user_info_url = "https://idp.example.com/oauth2/userinfo" + jwks_url = "https://idp.example.com/oauth2/jwks" + client_id = "<portal-client-id>" + callback_url = "https://portal.example.com/api-portal/<org-handle>/callback" + logout_url = "https://idp.example.com/oidc/logout" + scope = "openid profile email" + ``` + + Keep the client secret out of this file — reference it from a mounted file instead, and put the file on every VM: + + {% raw %} + + ```toml + client_secret = '{{ file "/secrets/api-portal/oidc_client_secret" }}' + ``` + + {% endraw %} + +Both the callback and logout redirect URLs must sit under the portal's `/api-portal` mount and match what you registered in the identity provider. + +The OIDC endpoints have no defaults on purpose, and the portal refuses to start in `idp` mode without `issuer`, `authorizationUrl`, `tokenUrl`, `clientId`, and `callbackUrl`. See [Connect an identity provider](../setting-up/authentication/connect-an-identity-provider.md). + +## Authorization + +Authorization is separate from authentication and applies in both modes. Keep it on: + +```yaml +api-portal-ui: + config: + auth: + authorization: + enabled: true + mode: role +``` + +`enabled: false` makes any authenticated caller satisfy every Management API operation's scope list. It is a development opt-out and logs a startup warning. + +In the default `role` mode the portal expands the token's roles claim through a grant table and ignores the token's own scope claim, so a caller can't widen a role's grant by requesting extra scopes. Mount your grant table rather than baking it into an image, so changing what a role may do is a restart rather than a rebuild. See [Authentication](../setting-up/authentication/overview.md). + +## Try-it proxy + +The try-it console calls an API's registered endpoint server-side, through a same-origin proxy, so every gateway doesn't need CORS headers naming the portal. Two of its defaults are development-oriented and should be tightened in production: + +```toml +[api_portal.tryout] +allow_http_endpoints = false # default true — production should allow only https:// +allow_private_endpoints = false # default; leave false unless the gateway is genuinely private +tls_skip_verify = false # development only +timeout_ms = 15000 +max_request_bytes = 1048576 +max_response_bytes = 5242880 +``` + +`allow_private_endpoints` is deny-by-default because the registered-endpoint allowlist can't protect against an endpoint registered to point at an internal service. Set it `true` only when the gateway legitimately sits on a private address — a cluster IP or in-cluster service name — and only after confirming which services the portal can reach. + +Two protections hold regardless of these settings: the proxy only calls URLs contained by an endpoint registered for that API, so a caller can't choose an arbitrary host; and link-local and cloud-metadata addresses such as `169.254.169.254` are refused at connection time. + +## Upload ceilings + +Uploads and archive extraction — theme ZIPs, API specifications, documents, landing-page content — are bounded by built-in defaults: + +```toml +[api_portal.uploads] +max_bytes = 10485760 # 10 MiB per upload, or per entry inside an archive +max_total_bytes = 52428800 # 50 MiB total extracted size per archive +max_zip_entries = 500 +max_depth = 10 +``` + +`max_total_bytes`, `max_zip_entries`, and `max_depth` are what guard archive extraction against a decompression bomb. Raise them only deliberately, and only as far as a legitimate artifact needs. + +## Related + +- [Database Configuration](database-configuration.md): TLS to the database and the password Secret +- [Control Plane Connection](control-plane-connection.md): securing the portal-to-Platform-API hop +- [Configurations](../references/configurations.md): every key referenced here diff --git a/en/mkdocs.yml b/en/mkdocs.yml index 16131dd91..460effba7 100644 --- a/en/mkdocs.yml +++ b/en/mkdocs.yml @@ -741,6 +741,14 @@ nav: - Theming: api-portal/next/admin-settings/theming.md - Apply a Theme to a View: api-portal/next/admin-settings/apply-a-theme.md - Design Mode: api-portal/next/admin-settings/design-mode.md + # Take it to production. + - Deployment: + - Overview: api-portal/next/deployment/overview.md + - Security Hardening: api-portal/next/deployment/security-hardening.md + - Database Configuration: api-portal/next/deployment/database-configuration.md + - Resources & Scaling: api-portal/next/deployment/resources-and-scaling.md + - Deploy & Verify: api-portal/next/deployment/deploy-and-verify.md + - Control Plane Connection: api-portal/next/deployment/control-plane-connection.md - References: - Management API: - Overview: api-portal/next/rest-api/overview.md @@ -822,6 +830,14 @@ nav: - Theming: api-portal/1.0.0/admin-settings/theming.md - Apply a Theme to a View: api-portal/1.0.0/admin-settings/apply-a-theme.md - Design Mode: api-portal/1.0.0/admin-settings/design-mode.md + # Take it to production. + - Deployment: + - Overview: api-portal/1.0.0/deployment/overview.md + - Security Hardening: api-portal/1.0.0/deployment/security-hardening.md + - Database Configuration: api-portal/1.0.0/deployment/database-configuration.md + - Resources & Scaling: api-portal/1.0.0/deployment/resources-and-scaling.md + - Deploy & Verify: api-portal/1.0.0/deployment/deploy-and-verify.md + - Control Plane Connection: api-portal/1.0.0/deployment/control-plane-connection.md - References: - Management API: - Overview: api-portal/1.0.0/rest-api/overview.md From 1d0e017be19fcbf96ec4ab12b5ae88fc1554db87 Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake <piumal1999@gmail.com> Date: Fri, 7 Aug 2026 15:37:20 +0530 Subject: [PATCH 11/11] Update sidebar item name --- en/mkdocs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/en/mkdocs.yml b/en/mkdocs.yml index 460effba7..783ed0961 100644 --- a/en/mkdocs.yml +++ b/en/mkdocs.yml @@ -685,7 +685,7 @@ nav: - Moesif Analytics: ai-gateway/1.0.0/analytics/moesif-analytics.md - Analytics Header Filter: ai-gateway/1.0.0/analytics/analytics-header-filter.md # In-development 'next' doc sets, only shown in the nav while browsing a - - API Portal & MCP Hub: + - API Portal: - "next": - Overview: api-portal/next/overview.md - About this Release: api-portal/next/about-this-release.md @@ -1462,7 +1462,7 @@ extra: # here is rendered with a version selector; its direct children must be the # version groups (titled with the version string). See nav-item.html / theme.js. versioned_sections: - API Portal & MCP Hub: + API Portal: slug: api-portal default: "1.0.0" versions: @@ -1512,7 +1512,7 @@ extra: - title: Developer Portal options: - verticle-line - - title: API Portal & MCP Hub + - title: API Portal options: - verticle-line - divider @@ -1630,7 +1630,7 @@ extra: Developer Portal: icon: octicons/book-16 level: 1 - API Portal & MCP Hub: + API Portal: icon: octicons/book-16 level: 1 Analytics & Monetization: