From 8b473a1c132fe73dfdb1cf67ef14d9ed6121f74d Mon Sep 17 00:00:00 2001 From: Viljami Kuosmanen Date: Fri, 27 Mar 2026 12:06:55 +0000 Subject: [PATCH 1/2] docs: add comprehensive Environments & Secrets documentation Add a new top-level documentation page for the Environments & Secrets feature, covering variable types, naming conventions, all supported contexts (webhooks, automations, templates, ERP integrations, portals), Blueprint interaction, API reference with correct /v1/environments paths, SDK usage, security model, and common patterns. Refactor the existing webhook-scoped page to focus on webhook-specific usage and link to the new comprehensive page. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/environments/_category_.json | 4 + docs/environments/environments-secrets.md | 311 ++++++++++++++++++ .../webhooks/environments-secrets.md | 142 ++------ sidebars.js | 1 + 4 files changed, 342 insertions(+), 116 deletions(-) create mode 100644 docs/environments/_category_.json create mode 100644 docs/environments/environments-secrets.md diff --git a/docs/environments/_category_.json b/docs/environments/_category_.json new file mode 100644 index 0000000..8b7e8ed --- /dev/null +++ b/docs/environments/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Environments & Secrets", + "position": 5 +} diff --git a/docs/environments/environments-secrets.md b/docs/environments/environments-secrets.md new file mode 100644 index 0000000..08d9cc1 --- /dev/null +++ b/docs/environments/environments-secrets.md @@ -0,0 +1,311 @@ +--- +title: Environments & Secrets +sidebar_position: 1 +--- + +# Environments & Secrets + +[[API Docs](/api/environments)] +[[SDK](https://www.npmjs.com/package/@epilot/environments-client)] + +Environments & Secrets is an organization-level key-value store for configuration and credentials. Define a variable once, then reference it anywhere using `{{ env.variable_key }}` syntax. Values are resolved at runtime, so the same configuration works across sandbox and production without changes. + +You can manage your environment variables in the epilot portal under [Configuration > Environments](https://portal.epilot.cloud/app/settings/environments). + +## Why use environment variables? + +Without environment variables, credentials and URLs are stored directly inside webhooks, templates, and integrations. This causes problems: + +- **Duplication** -- The same API key appears in dozens of webhooks. When you rotate it, you must update every one. +- **Blueprint sync issues** -- Blueprints copy configuration between sandbox and production. Hardcoded sandbox URLs end up in production, breaking integrations. +- **Security risk** -- Credentials scattered across configs are harder to audit and easier to leak. + +Environment variables solve all three. Store your credentials and URLs as variables, reference them with `{{ env.* }}`, and each organization resolves them to its own values. + +## Variable types + +| Type | Description | Readable? | +|------|-------------|-----------| +| **String** | Plain text value. Use for URLs, IDs, feature flags, and other non-sensitive config. | Yes -- value is visible in the UI and returned by the API. | +| **SecretString** | Encrypted value. Use for API keys, OAuth secrets, passwords, and tokens. | No -- value is write-only. Once saved, it cannot be read back through the UI or API. | + +## Naming your variables + +Variable keys use **dot-separated namespacing** to keep things organized: + +``` +erp_api.base_url +erp_api.oauth_token_url +erp_api.oauth_secret +portal.domain +n8n.metering_webhook_url +``` + +**Rules:** +- Start with a lowercase letter or digit +- Use only lowercase letters, digits, dots (`.`), underscores (`_`), and hyphens (`-`) +- Maximum 128 characters +- Pattern: `^[a-z0-9][a-z0-9_.-]{0,127}$` + +:::tip +Use the dot prefix to group related variables. For example, prefix all ERP-related variables with `erp_api.` so they appear together in the UI. +::: + +## Referencing variables + +Use double curly braces with the `env` namespace to reference a variable: + +``` +{{ env.erp_api.base_url }} +``` + +Spaces around the key are optional -- `{{ env.key }}` and `{{env.key}}` both work. + +### Where you can use `{{ env.* }}` + +Environment variables are supported in the following contexts: + +| Context | Example use | +|---------|-------------| +| **Webhooks** | URL, headers, authentication credentials (Basic Auth, API Key, OAuth) | +| **Automation flows** | Custom action payloads, external integration parameters | +| **Email & document templates** | Portal URLs, shared links, environment-specific content | +| **Entity mapping** | Transformation rules, target URLs | +| **ERP integrations** | Base URLs, OAuth endpoints, file proxy configuration | +| **Customer portal** | Hook execution, link resolution, template variables | + +### Resolution rules + +- Variables are resolved **server-side at runtime**. The `{{ env.* }}` placeholder is replaced with the actual value just before the action executes. +- **SecretString** values are only resolved in trusted backend contexts (webhooks, integrations, automation actions). They are never sent to the browser or included in template previews. +- If a referenced variable does not exist, the placeholder remains unresolved and the action fails with an error. + +## Working with Blueprints + +Environment variables are **excluded from Blueprints** by design: + +- **Export** does not include environment variable values +- **Import** never overwrites environment variables in the target organization + +This is the key difference from Custom Variables, which transfer with Blueprints. After a Blueprint sync, custom variables carry sandbox values into production. Environment variables avoid this -- each organization keeps its own values. + +**Example workflow:** + +1. In your **sandbox**, create variables like `erp_api.base_url = https://sandbox.erp.example.com` +2. Configure webhooks using `{{ env.erp_api.base_url }}` +3. Export the configuration as a Blueprint and import it into **production** +4. In production, create the same variable key: `erp_api.base_url = https://erp.example.com` +5. The same webhook config now works in both environments -- no manual changes needed + +## Managing variables in the UI + +Navigate to [Configuration > Environments](https://portal.epilot.cloud/app/settings/environments) to manage your variables. + +From this page you can: + +- **Create** new variables with a key, type, value, and optional description +- **Edit** existing variable values and descriptions +- **Delete** variables you no longer need +- **Search and filter** by key name +- **Copy** the `{{ env.key }}` syntax to paste into webhooks or templates + +Variables are grouped by their namespace prefix (the part before the first dot) and displayed in collapsible sections. + +:::caution +Deleting or renaming a variable breaks any configuration that references it. Check your webhooks, templates, and automations before removing a variable. +::: + +## API reference + +Base URL: `https://environments.sls.epilot.io` + +| Operation | Method | Path | Description | +|-----------|--------|------|-------------| +| List variables | `GET` | `/v1/environments` | Returns all variables (metadata only, secret values omitted) | +| Create variable | `POST` | `/v1/environments` | Create a new variable. Returns `409` if the key already exists | +| Get variable | `GET` | `/v1/environments/{key}` | Get a variable by key. Value included only for `String` type | +| Update variable | `PUT` | `/v1/environments/{key}` | Update a variable value. Creates the variable if it doesn't exist | +| Delete variable | `DELETE` | `/v1/environments/{key}` | Delete a variable by key | + +### Authentication + +All requests require a Bearer token in the `Authorization` header: + +``` +Authorization: Bearer +``` + +### Permissions + +| Permission | Required for | +|------------|-------------| +| `environments:edit` | List, create, get, and update variables | +| `environments:delete` | Delete variables | + +## Examples + +### Create a String variable + +```bash +curl -X POST https://environments.sls.epilot.io/v1/environments \ + -H "Authorization: Bearer $EPILOT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "key": "erp_api.base_url", + "type": "String", + "value": "https://erp.example.com/api/v1", + "description": "ERP API base URL" + }' +``` + +**Response:** + +```json +{ + "key": "erp_api.base_url", + "type": "String", + "value": "https://erp.example.com/api/v1", + "description": "ERP API base URL", + "created_at": "2026-03-01T12:00:00Z", + "updated_at": "2026-03-01T12:00:00Z" +} +``` + +### Create a SecretString variable + +```bash +curl -X POST https://environments.sls.epilot.io/v1/environments \ + -H "Authorization: Bearer $EPILOT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "key": "erp_api.oauth_secret", + "type": "SecretString", + "value": "sk_live_abc123...", + "description": "ERP OAuth client secret" + }' +``` + +**Response** (note: value is omitted for secrets): + +```json +{ + "key": "erp_api.oauth_secret", + "type": "SecretString", + "description": "ERP OAuth client secret", + "created_at": "2026-03-01T12:00:00Z", + "updated_at": "2026-03-01T12:00:00Z" +} +``` + +### Use variables in a webhook + +Configure a webhook that uses environment variables for the URL and authentication: + +```json title="Webhook configuration" +{ + "url": "{{ env.erp_api.base_url }}/webhooks/orders", + "authentication": { + "type": "oauth2", + "token_url": "{{ env.erp_api.oauth_token_url }}", + "client_id": "{{ env.erp_api.oauth_app_id }}", + "client_secret": "{{ env.erp_api.oauth_secret }}" + }, + "headers": { + "X-Custom-Header": "{{ env.erp_api.tenant_id }}" + } +} +``` + +When the webhook fires, all `{{ env.* }}` references are replaced with the organization's actual values before the HTTP request is sent. + +### Use variables in an email template + +Reference environment variables in email and document templates for environment-specific content: + +```html title="Email template" +

+ View your order in the + customer portal. +

+``` + +In sandbox, `portal.domain` might resolve to `sandbox.mycompany.epilot.cloud`. In production, it resolves to `portal.mycompany.com`. + +### Update a variable value + +Rotating a credential is a single API call. Every webhook and integration that references the variable picks up the new value immediately: + +```bash +curl -X PUT https://environments.sls.epilot.io/v1/environments/erp_api.oauth_secret \ + -H "Authorization: Bearer $EPILOT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "value": "sk_live_new_rotated_key..." + }' +``` + +### Use the SDK + +```typescript +import { getClient } from '@epilot/environments-client'; + +const client = getClient(); +client.defaults.baseURL = 'https://environments.sls.epilot.io'; +client.defaults.headers.common['Authorization'] = `Bearer ${token}`; + +// List all variables +const { data } = await client.listEnvironmentVariables(); +console.log(data.items); + +// Create a variable +await client.createEnvironmentVariable(null, { + key: 'my_app.api_url', + type: 'String', + value: 'https://api.example.com', + description: 'My application API URL', +}); +``` + +## Security + +- **Encryption at rest** -- All values (including String type) are encrypted using per-organization AWS KMS keys with envelope encryption. +- **Write-only secrets** -- SecretString values cannot be read back through the API or UI after creation. To change a secret, you overwrite it with a new value. +- **Backend-only resolution** -- Secrets are resolved only in trusted server-side contexts. They are never sent to the browser. +- **No template preview** -- Secrets are excluded from template variable previews and the Template Variables API. +- **Log redaction** -- Secret values are automatically redacted from all application logs. + +## Common patterns + +### Credential rotation + +1. Update the secret value via the API or UI +2. All consumers pick up the new value immediately (within ~60 seconds due to caching) +3. No need to edit individual webhooks or integrations + +### Multi-environment setup with Blueprints + +1. Design your integrations in sandbox using `{{ env.* }}` references +2. Export as a Blueprint +3. Import the Blueprint into each target organization +4. In each organization, create the environment variables with org-specific values +5. The same configuration works everywhere + +### Organizing variables by integration + +Use dot-separated prefixes to group related variables: + +``` +# ERP integration +erp.base_url +erp.oauth_token_url +erp.oauth_client_id +erp.oauth_client_secret + +# Customer portal +portal.domain +portal.support_email + +# Metering +metering.webhook_url +metering.api_key +``` diff --git a/docs/integrations/webhooks/environments-secrets.md b/docs/integrations/webhooks/environments-secrets.md index bb20344..7bcf176 100644 --- a/docs/integrations/webhooks/environments-secrets.md +++ b/docs/integrations/webhooks/environments-secrets.md @@ -1,131 +1,33 @@ --- sidebar_position: 6 -title: "Environments & Secrets" +title: "Environments & Secrets in Webhooks" --- -# Environments & Secrets +# Environments & Secrets in Webhooks -Org-level configuration for storing environment variables and secrets, usable across webhooks, templates, and integrations. Environment variables let you define shared configuration once and reference it everywhere, avoiding duplication and enabling safe Blueprint syncs between sandbox and production. +:::tip +For the full guide on environment variables -- including setup, API reference, and all supported contexts -- see [Environments & Secrets](/docs/environments/environments-secrets). +::: -## Variable Types +Webhooks support `{{ env.* }}` references in URLs, headers, and authentication fields. This lets you keep credentials out of your webhook configuration and share the same setup across sandbox and production via Blueprints. -| Type | Behavior | -|------|----------| -| `String` | Readable key/value pair. Value returned by API. | -| `SecretString` | Write-only. Value is never returned by the API or interpolated into user-facing outputs. Consumed only by backend services. | +## Supported fields -## Hierarchical Namespacing +You can use environment variable references in: -Keys use dot-separated namespacing to group related variables: +- **Webhook URL** -- `{{ env.erp_api.base_url }}/webhooks/orders` +- **HTTP headers** -- `Authorization: Bearer {{ env.erp_api.api_key }}` +- **Basic Auth** -- username and password fields +- **API Key Auth** -- key value +- **OAuth 2.0** -- token URL, client ID, and client secret -``` -erp_api.oauth_token_url -erp_api.oauth_app_id -erp_api.oauth_secret -my_webhook.base_url -my_webhook.api_key -``` - -Key format: lowercase alphanumeric with dots, underscores, and hyphens. Max 128 characters. Pattern: `^[a-z0-9][a-z0-9_.-]{0,127}$` - -## Variable Resolution - -Reference environment variables in webhooks and templates using the `env` namespace: - -``` -{{ env.erp_api.base_url }} -{{ env.erp_api.oauth_token_url }} -{{ env.erp_api.oauth_secret }} -{{ env.n8n.metering_webhook_url }} -``` - -Resolution rules: - -- Variables are resolved **server-side at runtime** -- `SecretString` values are only available in trusted backend contexts (webhooks, integrations) -- The Template Variables API excludes secrets from its response - -## API Operations - -Base path: `/v2/organization/environments` - -| Operation | Method | Path | Description | -|-----------|--------|------|-------------| -| `listEnvironmentVariables` | `GET` | `/v2/organization/environments` | List all variables (metadata only, secret values omitted) | -| `createEnvironmentVariable` | `POST` | `/v2/organization/environments` | Create a new variable. Returns `409` if key already exists | -| `getEnvironmentVariable` | `GET` | `/v2/organization/environments/{key}` | Get a variable. Value included only for `String` type | -| `updateEnvironmentVariable` | `PUT` | `/v2/organization/environments/{key}` | Update a variable. Changes take effect immediately for all consumers | -| `deleteEnvironmentVariable` | `DELETE` | `/v2/organization/environments/{key}` | Delete a variable | - -## Blueprint Interaction - -Environment variables are **excluded from Blueprints**: - -- Export does not include environment variables -- Import never overwrites environment values -- This prevents sandbox credentials from leaking to production - -This is the key difference from Custom Variables, which transfer with Blueprints and can cause sandbox values to appear in production after a sync. - -## Use Cases - -### Sandbox/Production Parity +## Example -Deploy the same webhook configuration via Blueprints to both environments. URLs and credentials resolve to environment-specific values automatically, with no manual reconfiguration needed after import. - -### ERP Integration Credentials - -Define OAuth endpoints, API keys, and base URLs once as environment variables. All webhooks reference the same variables, so credential rotation requires updating a single value rather than editing every webhook individually. - -### Reusable Global Variables - -Store shared URLs, portal domains, and configuration flags as `String` variables. Reference them across templates and integrations without duplicating values or embedding them in complex handlebars logic. - -## Security Considerations - -- **KMS encryption** -- All values are encrypted at rest using per-org AWS KMS keys (envelope encryption) -- **Write-only secrets** -- `SecretString` values cannot be read back through the API -- **No frontend access** -- Secrets are resolved only in trusted backend contexts -- **No template interpolation** -- Secrets are never interpolated into user-facing template outputs -- **Log redaction** -- Secret values are redacted from all logs - -## Examples - -### Creating a Variable - -```bash title="Create a String variable" -curl -X POST https://api.epilot.io/v2/organization/environments \ - -H "Authorization: Bearer $EPILOT_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "key": "erp_api.base_url", - "type": "String", - "value": "https://erp.example.com/api/v1", - "description": "ERP API base URL" - }' -``` - -```bash title="Create a SecretString variable" -curl -X POST https://api.epilot.io/v2/organization/environments \ - -H "Authorization: Bearer $EPILOT_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "key": "erp_api.oauth_secret", - "type": "SecretString", - "value": "sk_live_abc123...", - "description": "ERP OAuth client secret" - }' -``` - -### Using in Webhook Configuration - -Reference environment variables in your webhook URL and authentication fields: - -```json title="Webhook config using environment variables" +```json title="Webhook using environment variables" { "url": "{{ env.erp_api.base_url }}/webhooks/orders", - "auth": { - "type": "oauth", + "authentication": { + "type": "oauth2", "token_url": "{{ env.erp_api.oauth_token_url }}", "client_id": "{{ env.erp_api.oauth_app_id }}", "client_secret": "{{ env.erp_api.oauth_secret }}" @@ -133,4 +35,12 @@ Reference environment variables in your webhook URL and authentication fields: } ``` -When this webhook fires, all `{{ env.* }}` references are resolved server-side to their org-specific values before the request is sent. +When the webhook fires, all `{{ env.* }}` references are resolved server-side to the organization's actual values before the HTTP request is sent. SecretString values are decrypted only at this point and never logged. + +## Autocomplete + +The webhook configuration UI provides autocomplete when you type `{{ env.`. It suggests matching variable keys from your organization and auto-completes the closing braces. + +## Error handling + +If a referenced variable does not exist in your organization, the webhook call fails with an error indicating which variable could not be resolved. Check your [Environments settings](https://portal.epilot.cloud/app/settings/environments) to verify the variable exists. diff --git a/sidebars.js b/sidebars.js index 7bc762c..7ef327d 100644 --- a/sidebars.js +++ b/sidebars.js @@ -36,6 +36,7 @@ module.exports = { items: [ { type: 'autogenerated', dirName: 'auth' }, { type: 'autogenerated', dirName: 'sso' }, + { type: 'autogenerated', dirName: 'environments' }, ], }, { From 79822ef2db2434f8acb6ba11ac2a9676f47abdc5 Mon Sep 17 00:00:00 2001 From: Viljami Kuosmanen Date: Fri, 27 Mar 2026 12:16:22 +0000 Subject: [PATCH 2/2] docs: use @epilot/sdk instead of @epilot/environments-client in examples Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/environments/environments-secrets.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/environments/environments-secrets.md b/docs/environments/environments-secrets.md index 08d9cc1..b10ddd0 100644 --- a/docs/environments/environments-secrets.md +++ b/docs/environments/environments-secrets.md @@ -6,7 +6,7 @@ sidebar_position: 1 # Environments & Secrets [[API Docs](/api/environments)] -[[SDK](https://www.npmjs.com/package/@epilot/environments-client)] +[[SDK](https://www.npmjs.com/package/@epilot/sdk)] Environments & Secrets is an organization-level key-value store for configuration and credentials. Define a variable once, then reference it anywhere using `{{ env.variable_key }}` syntax. Values are resolved at runtime, so the same configuration works across sandbox and production without changes. @@ -247,18 +247,16 @@ curl -X PUT https://environments.sls.epilot.io/v1/environments/erp_api.oauth_sec ### Use the SDK ```typescript -import { getClient } from '@epilot/environments-client'; +import { epilot } from '@epilot/sdk'; -const client = getClient(); -client.defaults.baseURL = 'https://environments.sls.epilot.io'; -client.defaults.headers.common['Authorization'] = `Bearer ${token}`; +epilot.authorize(accessToken); // List all variables -const { data } = await client.listEnvironmentVariables(); +const { data } = await epilot.environments.listEnvironmentVariables(); console.log(data.items); // Create a variable -await client.createEnvironmentVariable(null, { +await epilot.environments.createEnvironmentVariable(null, { key: 'my_app.api_url', type: 'String', value: 'https://api.example.com', @@ -266,6 +264,16 @@ await client.createEnvironmentVariable(null, { }); ``` +You can also use the tree-shakeable import: + +```typescript +import { environments, authorize } from '@epilot/sdk/environments'; + +authorize(accessToken); + +const { data } = await environments.listEnvironmentVariables(); +``` + ## Security - **Encryption at rest** -- All values (including String type) are encrypted using per-organization AWS KMS keys with envelope encryption.