Technical docs
Variables & Secrets
Public reference generated from tech docs/variables.md.
Overview
- Last verified on: 2026-05-11 - Repos checked: `frontend`, `backend` - Runtime evidence checked: no
1. What it is
`/variables` is the organization-scoped surface for managing rows in `organization_variables`. Each row stores a `placeholder` key, a `value`, and an `is_secret` flag that controls whether the value is returned raw or obfuscated outside the backend.
This surface is for organization variables only. It does not manage developer-only system settings from `/defaults`.
2. Why support cares
- A variable appears missing, unchanged, or undeletable on `/variables`. - A user expects to see a secret again after saving it, but only sees masked bullets. - A workflow, tool call, or prompt still contains `{{NAME}}` or fails because a variable is missing. - A workflow cloned into another organization stops working until cloned variable values are re-entered.
3. System model
- Source of truth is the backend `OrganizationVariable` model backed by the `organization_variables` table. - Important fields are `organization_id`, `placeholder`, `value`, `is_secret`, `created_at`, `updated_at`, and soft-delete field `deleted_at`. - Active-row uniqueness is enforced on `(organization_id, placeholder)` only when `deleted_at` is `null`. - API responses and variable WebSocket payloads use `OrganizationVariable.safeData()`. Public values are returned as stored; secret values are obfuscated before leaving the backend. - The `/variables` page keeps client state in a React Query cache keyed by `['variables', orgId]`. - The page listens for `variable.created`, `variable.updated`, and `variable.deleted` to keep the table in sync. - Prompt and workflow placeholder replacement reads `{{PLACEHOLDER}}` tokens. - There are two different placeholder families that support must not mix up: - Bare placeholders such as `{{API_KEY}}` are organization-variable lookups. - Dotted workflow placeholders such as `{{input.email}}`, `{{state.customerId}}`, `{{output.results[0]}}`, and `{{resume.message}}` are runtime workflow/context lookups, not organization-variable names. - Workflow validation checks whether referenced organization variables exist. - `programming.run_code` can expose named organization variables inside the sandbox through `env_variables`. - Workflow cloning across organizations copies referenced variable definitions into the target organization.
4. Expected behavior
- `/variables` loads `GET /api/organizations/{orgId}/variables` without paging params. Route validation defines default query values `page=1` and `limit=10`, so the page loads only the first validated backend page. - Backend list order is `created_at DESC`. The frontend also defaults the table sort to `Added` descending. - The page title is `Variables & Secrets`. The primary action is `Add`. - The create dialog sends `placeholder`, `value`, and `is_secret`. - Create-dialog key sanitization is frontend-only. It strips non-alphanumeric and non-underscore characters and prepends `_` if the first character is a digit. - The current create dialog does not auto-uppercase the key. - Placeholder matching is case-sensitive everywhere. `API_KEY` and `api_key` are different keys. - Backend create/update validation does not enforce the same naming contract as the UI. Direct API callers can create placeholder names that the `/variables` page would never let a user type. - Safest support guidance for org-variable names is: use only letters, numbers, and underscores; avoid dots, brackets, and hyphens for user-facing/shared variables. - Create rejects active duplicates and reserved placeholders `role`, `goal`, and `instructions`. - The edit flow is value-only in practice. The key field is read-only, the type control is display-only, and the backend update schema accepts only `value`. - Secret rows stay masked in the table, export, API responses, and variable WebSocket events. - Table export masks secret values as `••••••••`. The table cell renderer masks them visually as bullets as well. - Single-row delete in the UI uses `POST /variables/bulk-delete` with one id, not the single-row `DELETE` endpoint. - Delete is optimistic in the UI. The row is removed immediately, then the page shows an undo toast that calls `POST /variables/bulk-restore`. - There is no deleted-items view on `/variables`. Restore is available only through the immediate undo flow or direct API use. - General prompt and workflow placeholder processing leaves `{{NAME}}` unchanged if the variable is missing. - Secret substitution is disabled by default in general prompt processing. - Secret substitution is explicitly enabled for workflow `run_code`, the `http.http_request` tool, MCP server URL/header preparation, and code/tool paths that request sandbox `env_variables`. - Workflow runtime placeholders have their own source rules: - `input.*`, `state.*`, and `output.*` are resolved from workflow runtime data. - `resume.*` is only valid in `user_input` resume handling. - Unknown dotted source types fail workflow validation instead of falling back to org-variable lookup. - When a workflow is cloned into a different organization, referenced variables are cloned by placeholder with empty values in the target organization. This applies to both public and secret variables in the current implementation.
5. Edge cases and failure modes
- Editing a secret never reveals the previously stored raw value. Users must enter the replacement value they want saved. - Duplicate protection applies only to active rows. A placeholder can be recreated after soft deletion because the unique index excludes deleted rows. - A variable name can be valid in the `/variables` UI but still be a bad choice for workflow use if support or API callers create dotted names such as `foo.bar`. Workflow validation interprets dotted placeholders as runtime namespaces instead of org-variable names. - The current single-item `DELETE /api/organizations/{orgId}/variables/{variable_id}` action reads param `id` instead of `variable_id`. Direct callers can hit a false `Organization variable not found` path even when the row exists. - Workflow validation adds `Variable 'NAME' not found in organization variables` when a flow references a missing org variable. - Workflow validation treats `{{input.*}}`, `{{state.*}}`, `{{output.*}}`, and `{{resume.*}}` differently from org variables. A support report that says "variable missing" may actually be a runtime-schema or state-initialization issue. - `programming.run_code` fails hard when any requested `env_variables` name does not exist in the organization. - A cloned workflow can still fail in the target organization until cloned variable values are manually re-entered there. - The page can look incomplete for organizations with more than one backend page because the current frontend fetches only the first page.
6. Access and permissions
- All org-variable routes use backend middleware `checkOrganizationAccess`. - That middleware allows any authenticated user who belongs to the target organization. - The `/variables` route is not developer-gated in frontend navigation. - `/defaults` is a separate developer-gated `/sys` surface. It uses `/api/admin/settings` and is not managed by org-variable routes. - Support can verify org-membership access errors, list/create/update/delete behavior, masking behavior, and UI/WebSocket freshness from the page and org API routes. - Support needs engineering escalation for backend route bugs, runtime placeholder-substitution bugs, and restore/create conflicts that require backend or database inspection.
7. Observability and verification
- First check: classify the placeholder before debugging substitution: - Bare `{{NAME}}` means org-variable lookup. - Dotted `{{input.*}}`, `{{state.*}}`, `{{output.*}}`, `{{resume.*}}` means workflow runtime lookup. - Second check: call `GET /api/organizations/{orgId}/variables` and confirm whether the row exists, whether `is_secret` matches expectation, and whether the returned value is correctly masked for secret rows. - Third check: if the issue is in workflow execution, verify whether the placeholder name itself is safe for org-variable use. Names with dots are especially suspicious because workflow validation treats them as runtime paths. - Fourth check: verify whether the UI receives `variable.created`, `variable.updated`, or `variable.deleted` and whether the React Query cache is refetched or updated after the change. - Escalation check for downstream breakage: inspect the consumer that uses the variable. - Workflow-save failures surface through validation errors such as `Variable 'NAME' not found in organization variables`. - Sandbox code execution can fail with `Organization variables not found: ...` when `env_variables` requests unknown names. - Cross-org clone issues usually require checking whether the target organization has blank cloned variables that were never filled in.
8. Contracts and limits
- `GET /api/organizations/{orgId}/variables` - `POST /api/organizations/{orgId}/variables` - `PUT /api/organizations/{orgId}/variables/{variable_id}` - `DELETE /api/organizations/{orgId}/variables/{variable_id}` - `POST /api/organizations/{orgId}/variables/bulk-delete` - `POST /api/organizations/{orgId}/variables/bulk-restore` - `GET /api/organizations/{orgId}/variables/deleted` - List query accepts `page`, `limit`, optional `placeholder`, and optional `is_secret`. Validation allows `limit` from `1` to `100`. - Create requires `placeholder: string`, `value: string`, and `is_secret: boolean`. - Update accepts only `value: string`. - Bulk delete and bulk restore require `ids` as a non-empty array of positive integers. - Runtime placeholder syntax for organization variables is `{{NAME}}`. - Secret raw values are never returned through `safeData()` responses from this surface.
9. Known boundaries
- This surface does not manage system defaults or admin settings. - This surface does not provide a UI for browsing deleted variables. - This surface does not provide a way to reveal an existing secret's raw stored value after creation. - The current update path does not support changing a variable key or toggling its type. - The current frontend does not fetch beyond the first backend page.
10. Related docs
- `defaults.md` - `agent-tools.md` - `agent-loop.md` - `workflow-engine.md`