- API Portal
- next
- Deployment
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.
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:
targetUrlmust be the receiver's webhook endpoint, reachable from the portal's network. For a Platform API, that is/api/internal/v0.9/webhook/eventson its host.secretmust equal the receiver's configured webhook secret —APIP_CP_WEBHOOK_SECRETon 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.
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.
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:
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.
[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: registering and verifying subscribers
- Authentication: why production uses an identity provider
- Security Hardening: the identity-provider configuration that replaces local auth