You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
/api-portals Resource — API Portal Addition Proposal
#3400
A new resource in platform-api: /api-portals. Registers an existing WSO2 API Portal instance so platform-api can publish to it. Same portal software as the standalone OSS devportal — this doesn't introduce a new component, it just gives platform-api a first-class way to know about a portal and treat it as a publishing target.
"Publishing" means pushing APIs, API content, MCP Servers, MCP Server content, and Subscription Plans from platform-api into the portal via the portal's admin REST APIs.
Why
Today platform-api has no concept of "a portal I can publish to." APIs / MCP Servers / plans / content live in platform-api and there's no first-class way to push them out to a customer's API Portal. Adding /api-portals gives REST-managed registration of one or more portals per organisation.
REST surface
Follows existing platform-api OpenAPI conventions: camelCase wire, standard error envelope, ap:* scope prefix, standard pagination on list.
POST /api-portals — register a portal.
GET /api-portals — list portals in the caller's org.
GET /api-portals/{apiPortalId} — read one.
PUT /api-portals/{apiPortalId} — update mutable fields.
DELETE /api-portals/{apiPortalId} — deregister.
Org is resolved from the JWT — never accepted from the client.
id = handle on the wire. Handle is a URL-friendly slug, unique within org, immutable after creation. Matches the gateway resource (gatewayId = handle). DB-side UUID is not exposed.
handle validation reuses utils.ValidateHandle — 3–40 chars, lowercase alphanumeric + hyphens, no leading / trailing / consecutive hyphens.
status — pending while being brought up / verified, active once reachable, failed on error. In OSS the transition is essentially "operator says it's ready" — no health-check polling.
metadata — opaque free-form pass-through (additionalProperties: true). Platform-api stores and returns it as-is; not consumed by any core code path. Useful for operator-set labels or fields that customer tooling wants to attach.
url is nullable at create (the portal may not be reachable yet) but becomes required once status = active.
Auth material (the credential platform-api uses to authenticate to the portal on outbound publishing calls) proposed in the discussion here.
Scopes
Five new scopes, following the ap:<resource>:<action> convention:
ap:api_portal:read Read API Portals
ap:api_portal:create Create an API Portal
ap:api_portal:update Update an API Portal
ap:api_portal:delete Delete an API Portal
ap:api_portal:manage Full access to API Portals
Every operation lists the fine-grained scope AND ap:api_portal:manage (OR-evaluated), matching platform-api's uniform :manage superset pattern used for gateways / rest_api / etc.
DB shape
New table api_portals. Follows platform-api house style: uuid VARCHAR(40) PK, organization_uuid FK cascade-delete, display_name on the DB side, opaque config as BYTEA, audit + timestamp columns, TIMESTAMPTZ on postgres.
CREATETABLEIF NOT EXISTS api_portals (
uuid VARCHAR(40) PRIMARY KEY,
organization_uuid VARCHAR(40) NOT NULL,
handle VARCHAR(40) NOT NULL,
display_name VARCHAR(255) NOT NULL,
description VARCHAR(1023),
url VARCHAR(500),
status VARCHAR(20) NOT NULL DEFAULT 'pending',
internal_auth_secret BYTEANOT NULL, -- outbound auth credential (shape proposed separately)
metadata BYTEANOT NULL, -- opaque pass-through
created_by VARCHAR(200),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_by VARCHAR(200),
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (organization_uuid) REFERENCES organizations(uuid) ON DELETE CASCADE,
UNIQUE (organization_uuid, handle)
);
CREATEINDEXIF NOT EXISTS idx_api_portals_org ON api_portals(organization_uuid);
Sqlite + sqlserver siblings ship alongside, matching every other table (sqlite uses DATETIME; sqlserver uses DATETIME2(7) + IF OBJECT_ID guards).
Wire-to-DB mapping: id and handle both map to the handle column; name maps to display_name; camelCase ↔ snake_case at the DAO boundary.
Publishing
Once a portal is registered, platform-api can publish to it.
The publishing code path and the outbound-auth model (how platform-api authenticates to the portal for these calls) are covered in #3399 and #3242
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
What
A new resource in
platform-api:/api-portals. Registers an existing WSO2 API Portal instance so platform-api can publish to it. Same portal software as the standalone OSS devportal — this doesn't introduce a new component, it just gives platform-api a first-class way to know about a portal and treat it as a publishing target."Publishing" means pushing APIs, API content, MCP Servers, MCP Server content, and Subscription Plans from platform-api into the portal via the portal's admin REST APIs.
Why
Today platform-api has no concept of "a portal I can publish to." APIs / MCP Servers / plans / content live in platform-api and there's no first-class way to push them out to a customer's API Portal. Adding
/api-portalsgives REST-managed registration of one or more portals per organisation.REST surface
Follows existing platform-api OpenAPI conventions: camelCase wire, standard error envelope,
ap:*scope prefix, standard pagination on list.POST /api-portals— register a portal.GET /api-portals— list portals in the caller's org.GET /api-portals/{apiPortalId}— read one.PUT /api-portals/{apiPortalId}— update mutable fields.DELETE /api-portals/{apiPortalId}— deregister.Org is resolved from the JWT — never accepted from the client.
Fields
ApiPortalResponseshape:{ "id": "acme-portal", "name": "Acme Developer Portal", "handle": "acme-portal", "description": "...", "url": "https://portal.example.com", "status": "pending" | "active" | "failed", "metadata": { }, "createdAt": "...", "updatedAt": "..." }Notes on the shape:
id=handleon the wire. Handle is a URL-friendly slug, unique within org, immutable after creation. Matches the gateway resource (gatewayId = handle). DB-side UUID is not exposed.handlevalidation reusesutils.ValidateHandle— 3–40 chars, lowercase alphanumeric + hyphens, no leading / trailing / consecutive hyphens.status—pendingwhile being brought up / verified,activeonce reachable,failedon error. In OSS the transition is essentially "operator says it's ready" — no health-check polling.metadata— opaque free-form pass-through (additionalProperties: true). Platform-api stores and returns it as-is; not consumed by any core code path. Useful for operator-set labels or fields that customer tooling wants to attach.urlis nullable at create (the portal may not be reachable yet) but becomes required oncestatus = active.Scopes
Five new scopes, following the
ap:<resource>:<action>convention:Every operation lists the fine-grained scope AND
ap:api_portal:manage(OR-evaluated), matching platform-api's uniform:managesuperset pattern used for gateways / rest_api / etc.DB shape
New table
api_portals. Follows platform-api house style:uuid VARCHAR(40)PK,organization_uuidFK cascade-delete,display_nameon the DB side, opaque config asBYTEA, audit + timestamp columns,TIMESTAMPTZon postgres.Sqlite + sqlserver siblings ship alongside, matching every other table (sqlite uses
DATETIME; sqlserver usesDATETIME2(7)+IF OBJECT_IDguards).Wire-to-DB mapping:
idandhandleboth map to thehandlecolumn;namemaps todisplay_name; camelCase ↔ snake_case at the DAO boundary.Publishing
Once a portal is registered, platform-api can publish to it.
The publishing code path and the outbound-auth model (how platform-api authenticates to the portal for these calls) are covered in #3399 and #3242
All reactions