S
supero.docs
Documentation/Managing Your App/Plugin Registry

Plugin Registry

Register your own external HTTP endpoint as a domain-scoped plugin, then call it through Supero with centralized credential storage, automatic retries, and a circuit breaker.

What the Plugin Registry Is

For most third-party services, the managed services catalog is the right place to start: browse it with GET /api/v1/services/catalog, import an entry with POST /api/v1/services/import, and configure it with PUT /api/v1/services/{id}/config. The Plugin Registry is the secondary, do-it-yourself path — use it when the vendor you need is not in the catalog, or when you want to wire up your own internal HTTP endpoint (an internal microservice, a partner webhook receiver, a custom automation) as something your app can call consistently.
A plugin is a record of one HTTP endpoint plus the settings needed to call it safely: method, timeout, an optional auth header, retry behavior, and circuit-breaker thresholds. Plugins are scoped to the domain, not to a project. POST /api/v1/plugins/register takes a project_uuid, but that value is not part of the plugin's address: the record is stored as a child of the domain under the two-element name [domain, plugin_name].

⚠️ Domain-scoped, not project-scoped

Every project in the domain sees every plugin. plugin_name must be unique across the whole domain, and GET /api/v1/plugins?project_uuid=... returns the domain's full plugin list regardless of the project_uuid you pass — the parameter is accepted for caller compatibility and does not filter. If you run several apps in one domain, namespace your plugin names yourself (storefront-sms, billing-sms) and assume every project can read every plugin record.

Register a Plugin

Register a plugin with a POST to /api/v1/plugins/register. project_uuid, plugin_name, and endpoint_url are required; everything else has a default.
bash
curl -X POST https://api.supero.dev/api/v1/plugins/register \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "project_uuid": "8f14e45f-2d3a-4b1c-9e6a-1234567890ab",
    "plugin_name": "internal-sms-gateway",
    "endpoint_url": "https://sms.example.com/api/send",
    "http_method": "POST",
    "auth_type": "header",
    "auth_header_name": "X-Api-Key",
    "auth_header_value": "your-vendor-api-key",
    "timeout_seconds": 15,
    "max_retries": 3
  }'
A successful call returns 201 with the new plugin_uuid:
json
{
  "success": true,
  "message": "Plugin registered successfully",
  "plugin": {
    "plugin_uuid": "c4a1...e92f",
    "plugin_name": "internal-sms-gateway",
    "endpoint_url": "https://sms.example.com/api/send",
    "enabled": true,
    "health_status": "healthy"
  }
}

⚠️ Unique per domain

plugin_name must be unique within the domain — registering the same name twice returns a 400, even when the two calls pass different project_uuid values. Two apps in one domain cannot both register internal-sms-gateway. endpoint_url must start with http:// or https://.

Plugin Fields

FieldTypeRequiredDefaultNotes
endpoint_urlstringYes—Full URL of the target endpoint (http:// or https://).
http_methodstringYesPOSTOne of POST, PUT, PATCH.
timeout_secondsintegerYes30Request timeout, up to 300 seconds.
auth_typestringYesnonenone or header.
auth_header_namestringNo—Header name to send when auth_type is header, e.g. X-Api-Key or Authorization.
auth_header_valuestringNo—Header value (API key, bearer token, etc). Redacted in the plugin API's own list and read responses; not encrypted at rest — see the warning below.
custom_headersjsonNo—Additional static headers, as a JSON object, sent with every call.
max_retriesintegerYes3Retry attempts on a failed call.
retry_backoff_secondsintegerYes2Initial backoff delay; increases exponentially with each retry.
circuit_breaker_thresholdintegerYes5Consecutive failures before the circuit opens.
circuit_breaker_timeout_secondsintegerYes60How long the circuit stays open before a call is allowed through again.
enabledbooleanYestrueDisabled plugins reject invocations with 503.

🚨 auth_header_value is not encrypted at rest

The credential you register here is stored as a plain string attribute on the plugin record. The platform's at-rest key store seals only integration service_config and platform_secret objects — plugin_registry is not in that set, so no sealing is applied to this field on any deployment. What the platform does provide is redaction: the plugin service substitutes ***REDACTED*** for the value in its own list and get responses. That is response shaping in one service, not a storage guarantee, and it is not a reason to treat the value as protected. Treat a registered plugin credential as a secret you are handing to the platform's database: use a short-lived, narrowly scoped, rotatable vendor credential, never a long-lived master key, and rotate it on the vendor's side if your domain's data is ever exposed. If the vendor is in the managed services catalog, configure it there instead — those credentials go through the service_config path, which the key store can seal.
The plugin API has no endpoint that changes auth_header_value — the only write routes on a registered plugin are PUT /status and DELETE. Rotating the credential means deleting the plugin and registering it again under the same name.
The registry also tracks read-only fields you'll see on a GET: plugin_uuid, health_status, total_invocations, success_count, failure_count, last_invocation_at, and average_latency_ms.

Managing Plugins

Method & PathPurpose
POST /api/v1/plugins/registerRegister a new plugin in the domain. project_uuid is required in the body but does not scope the record.
GET /api/v1/plugins?project_uuid={uuid}List the domain's plugins. project_uuid is accepted but does not filter — every project in the domain gets the same list. Add &enabled_only=true to return only enabled plugins.
GET /api/v1/plugins/{plugin_uuid}Get full details for one plugin.
PUT /api/v1/plugins/{plugin_uuid}/statusEnable or disable a plugin. Body: { "enabled": true }.
DELETE /api/v1/plugins/{plugin_uuid}Delete a plugin.
GET /api/v1/plugins/{plugin_uuid}/healthGet health status, invocation statistics, and circuit-breaker configuration.
bash
curl "https://api.supero.dev/api/v1/plugins?project_uuid=8f14e45f-2d3a-4b1c-9e6a-1234567890ab" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

curl -X PUT https://api.supero.dev/api/v1/plugins/c4a1...e92f/status \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled": false}'

Invoking a Plugin

To call a registered plugin, POST to /api/v1/plugins/invoke/{object_type}. object_type is just a label used for logging — use whatever name describes the call you're making. Identify the plugin with plugin_name or plugin_uuid in the body; every other field you send is forwarded as the request payload to the plugin's endpoint_url.
bash
curl -X POST https://api.supero.dev/api/v1/plugins/invoke/sms_notification \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "plugin_name": "internal-sms-gateway",
    "to": "+15551234567",
    "body": "Your order has shipped"
  }'
  • •The stored auth header (if auth_type is header) is attached automatically — the credential never has to travel through your application code.
  • •If the plugin is disabled, the call returns 503.
  • •If plugin_name or plugin_uuid does not resolve to a plugin in your domain, the call returns 404.
  • •plugin_name and plugin_uuid are stripped before the payload is forwarded — they are never sent to your endpoint.

ℹ️ A separate permission

Invoking a plugin requires the operational:create permission, which is distinct from the plugin:* permissions used to manage the registry — see Permissions below.

Reliability and Health

Every plugin call goes through the same reliability layer: on failure, the platform retries up to max_retries times with exponential backoff starting at retry_backoff_seconds. If failures reach circuit_breaker_threshold in a row, the circuit opens and further calls fail immediately — without a network round trip — until circuit_breaker_timeout_seconds has elapsed.
health_statusMeaning
healthyThe value a plugin is registered with — it is the schema default, so a brand-new plugin reports healthy before it has ever been called. Also the value while recent calls are succeeding.
unhealthyRecent calls are failing, but the circuit is not open.
circuit_openThe failure threshold was reached; calls are being short-circuited.
unknownDeclared in the health_status enum but not written on registration. Do not read it as "not yet checked" — as the registration response above shows, a newly registered plugin comes back healthy.
GET /api/v1/plugins/{plugin_uuid}/health returns the current health_status plus a statistics block (total_invocations, success_count, failure_count, success_rate_percent, average_latency_ms, last_invocation_at) and the plugin's circuit_breaker configuration (threshold, timeout_seconds).

Permissions

Managing the registry (register, update status, delete) and viewing it (list, read, health) are separate permissions, and invoking a plugin uses a third permission entirely. Across the roles you can assign to your own users:
RoleView pluginsRegister / update / deleteInvoke plugins
domain_adminYesYesYes
project_adminYesYesYes
domain_userYesNoYes
project_userYesNoYes
developerYesNoYes
tenant_adminYesNoYes
tenant_userYesNoYes
viewerYesNoNo
In practice: only domain_admin and project_admin can add, change, or remove plugins. Every other role can see what is registered; viewer can see the list but cannot invoke. So a developer or tenant_admin building against a plugin can call it but cannot change how it is configured, and a viewer can see that it exists without being able to run it.

Next Steps