Managing variables
Creating, overriding, updating and deleting tenant variables, and what the API returns for a secret.
The shape of a variable
| Field | Notes |
|---|---|
| Name | How it is referenced: ${vars.NAME} or ${secrets.NAME}. |
| Value | What it resolves to. |
| Secret | Whether it is a credential. Decides which prefix reaches it and how it is returned. |
| Key | Optional. The signing key this row overrides for. Absent means tenant-wide default. |
Name and key together identify a row: at most one default per name, and at most one override per name per key. Both are enforced by the database rather than checked in the application, so two requests racing to create the same override cannot both win.
Naming
Names are referenced from tool configurations by hand, so they are part of an interface. Conventions that save trouble later:
- Upper snake case, like an operating system environment variable.
STRIPE_API_KEY, notstripeApiKey. - Name the service, not the tool.
SALESFORCE_TOKENcan be shared by five tools;LEAD_LOOKUP_TOKENsuggests it cannot. - Do not encode the environment in the name.
API_KEYholding different values in your dev and production tenants is the point.PROD_API_KEYin the dev tenant is a mistake waiting to be made.
Secret or not
The flag changes three things: which prefix resolves it, how it is stored, and what the API gives back.
vars | secrets | |
|---|---|---|
| Referenced as | ${vars.NAME} | ${secrets.NAME} |
| Stored | As given | Encrypted |
| Returned by the API | The value | ******** |
| Readable back | Yes | No. Not by any endpoint |
A secret's real value leaves the database in exactly one direction: into the resolver, when a tool or channel actually uses it. No endpoint returns it, which means you cannot look a secret up later.
Keep the authoritative copy wherever you got it from, a password manager or the provider's own
console. If you only have it here, you do not have it: the API answers ******** and the only
recovery is to set a new value.
Mark anything credential-shaped as secret even when it feels harmless. A listing is the sort of thing that ends up in a screen recording or a support ticket, and the flag is what keeps the value out of it.
Overrides
Create the default first, then add overrides for the keys that need something else. An override is the same name with a signing key attached.
# The tenant-wide default
curl -X POST https://service.ensembleapp.dev/api/environments \
-H 'Authorization: Bearer <jwt>' \
-H 'Content-Type: application/json' \
-d '{ "name": "API_TOKEN", "value": "shared-token", "isSecret": true }'
# What one integrator gets instead
curl -X POST https://service.ensembleapp.dev/api/environments \
-H 'Authorization: Bearer <jwt>' \
-H 'Content-Type: application/json' \
-d '{ "name": "API_TOKEN", "value": "their-token", "isSecret": true, "keyId": "<signing key id>" }'Reading, updating and deleting follow the same rule: name alone addresses the default, and
?keyId= addresses that key's override.
curl https://service.ensembleapp.dev/api/environments/API_TOKEN \
-H 'Authorization: Bearer <jwt>'
curl 'https://service.ensembleapp.dev/api/environments/API_TOKEN?keyId=<signing key id>' \
-H 'Authorization: Bearer <jwt>'Omitting keyId always means the default. That is deliberate, so every caller written before
overrides existed keeps addressing exactly what it used to.
The API
GET /api/environments Every variable: defaults and overrides
GET /api/environments/{name} The default, or ?keyId= for one override
POST /api/environments Create a default, or an override with keyId
PUT /api/environments/{name} Update the default, or ?keyId= for one override
DELETE /api/environments/{name} Delete the default, or ?keyId= for one overrideDeleting
Deleting an override leaves the default, and callers on that key fall back to it. That is usually what you want when an integrator stops needing special treatment.
Deleting the default leaves overrides in place and anything without one resolving to nothing. A tool whose header becomes an empty string tends to fail in the service you are calling rather than here, so delete defaults deliberately.
Nothing warns you that a variable is still referenced. References live inside tool and channel configurations as text, so there is no link from the variable to its users. Before deleting one, search your tool definitions for the name.
Rotating a credential
Update the value in place. Tools resolve variables per call, so the next call picks it up with nothing to redeploy and no tool version to publish.
That is the main reason to keep credentials here rather than in a tool: a rotation is one edit rather than a change to every definition that uses it, each of which would otherwise need a new version.