> ## Documentation Index
> Fetch the complete documentation index at: https://gridos.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin secrets — per-user connector credentials

> Store Shopify / Stripe / GitHub / etc. credentials per-user instead of server-global. Values are write-only from the browser; GET only reports which slots are set, never the value.

Endpoints for the per-user plugin-BYOK feature. Secrets are stored in `public.user_plugin_secrets` (one row per `user_id, plugin_slug, secret_key`) and resolved at formula-evaluation time via `kernel.get_secret(slug, key)`.

All three endpoints are SaaS-only (404 in OSS; plugins fall back to env vars).

## GET /settings/plugin-secrets/\{plugin\_slug}

Returns the plugin's declared secret schema (from `manifest.json`) alongside a list of which slots the caller has filled. **Never returns the actual secret values.**

### Response

```json theme={null}
{
  "plugin_slug": "shopify",
  "declared": [
    {
      "key": "STORE_DOMAIN",
      "label": "Store domain",
      "placeholder": "myshop.myshopify.com",
      "help": "Your Shopify admin domain (no https://, no trailing slash)."
    },
    {
      "key": "ADMIN_TOKEN",
      "label": "Admin API token",
      "placeholder": "shpat_...",
      "help": "Private app admin access token..."
    }
  ],
  "set_keys": ["STORE_DOMAIN", "ADMIN_TOKEN"]
}
```

The Configure modal uses `declared` to render the input fields and `set_keys` to show a `✓ set` indicator on filled slots.

## PUT /settings/plugin-secrets/\{plugin\_slug}

Upsert a batch of secret key/value pairs for the caller's account.

### Request

<ParamField body="secrets" type="object" required>
  Map of `SECRET_KEY → value`. Empty-string values delete the matching row. Keys not present in the request are untouched, so the UI can save just the fields the user changed.
</ParamField>

```json theme={null}
{
  "secrets": {
    "STORE_DOMAIN": "myshop.myshopify.com",
    "ADMIN_TOKEN": "shpat_..."
  }
}
```

### Response

```json theme={null}
{
  "status": "Success",
  "plugin_slug": "shopify"
}
```

## DELETE /settings/plugin-secrets/\{plugin\_slug}

Wipe every secret the caller has stored for this plugin. Used by the Configure modal's **Disconnect** button.

### Response

```json theme={null}
{
  "status": "Success",
  "plugin_slug": "shopify"
}
```

## Security notes

* **Write-only from the client.** The GET endpoint only reports which slots are set, never the value. This means the Configure modal can't show the current value for editing — pasting a new value in a filled slot overwrites the old one, and clearing the slot deletes it. The trade-off: secrets never leave the server beyond the initial POST.
* **RLS + service-role key.** `public.user_plugin_secrets` has RLS policies allowing only the owner to SELECT/INSERT/UPDATE/DELETE their own rows. The server uses the service-role key for the hot read/write path; RLS is the defense-in-depth layer if a client key ever leaks.
* **Shared workbooks use the owner's secrets.** When a collaborator runs a connector formula on a shared workbook, the server resolves secrets for the workbook's owner (not the caller) so the data stays inside the owner's data boundary.

## Related

* [Plugin marketplace](/guides/marketplace) — the Configure modal this API powers.
* [Connectors](/guides/connectors) — the three shipped connectors (Shopify / Stripe / GitHub) and what credentials each needs.
