External Keys
A normal secret belongs to you: you store the value once, attach it to a tool, and every run - yours or a marketplace user’s - uses your credential. That is fine for a tool that talks to a service you pay for on purpose. It is the wrong shape for a tool that wraps a paid third-party API, where every marketplace run would be billed to your account.
An external key flips that around. The tool version declares a key by name and says what it is for; whoever runs the tool picks which of their own secrets fills it. The value never leaves the runner’s workspace, and your code reads the key exactly the same way it reads any other secret.
Declaring an external key
External keys are declared per version, from Studio’s Secrets tab (the same rows also appear in the strip under the file tree on the Code tab).
- Open the tool in Studio and go to Secrets.
- Add the key - either pick an existing workspace secret or create a new one - and turn on the External toggle on its row.
- Fill in the description. It is required, minimum 10 characters, and it is the line the runner reads next to the field they fill in, so write it for them: "Your OpenAI API key", not "key".
- Leave Optional off if the tool cannot run without the key. Turn it on if the tool degrades gracefully without it.
Declaring a key external does not publish your value to anyone. It publishes the name, the description, and whether it is optional.
What the runner sees
On the tool’s Run panel - in the marketplace, in the Preview, and in Studio’s own test panel - each declared key gets a row showing:
- the key name in monospace, with a red
*when it is required, or the wordoptionalwhen it is not; - your description underneath it;
- a searchable dropdown over the runner’s own secrets.
Any secret can fill any key - the names do not have to match. Once a secret is picked, the row gets a green Mapped badge and a line reading “Will use SECRET_NAME when running”.
The section only appears at all if the version actually declares external keys, and only fills in for a signed-in user - a signed-out visitor gets a Sign in prompt in its place.
Run is blocked while a required key is unmapped. The button is disabled and a line above it names what is missing: “Select a secret for OPENAI_API_KEY to run this tool.” Without that gate the key would arrive at the handler undefined, the tool would happily run on it, and the runner would be charged for the result.
Agents and workflows
Tools that declare external keys can still be attached to agents, sub-agents, and workflow steps. The mapping is stored per use, not per tool:
- Agents and sub-agents - right after you attach such a tool, a “<tool> needs a key” dialog asks which secret fills each declaration. The tool’s row keeps the dialog reachable afterwards, so you can change the credential without detaching. The choice covers that one agent.
- Workflow steps - the step’s settings show the same pickers, scoped to that step alone. Two steps in one workflow can run the same tool under different credentials.
A tool that carries its own keys shows none of this.
Calling a tool with external keys over HTTP
Over the API, the mapping travels in the X-Env-Keys header. Its value is a comma-separated list of KEY=secret_id pairs, where KEY is the declared name and secret_id is the id of the secret in your workspace that fills it:
curl -X POST https://cortexconnect.rival.io/api/v1/functions/fn_abc123/1.0/invoke \ -H "Authorization: your-api-key-here" \ -H "Content-Type: application/json" \ -H "X-Env-Keys: OPENAI_API_KEY=env_9f2c1a,SLACK_TOKEN=env_44b7de" \ -d '{ "event": { "name": "world" } }'You do not have to assemble that by hand. Studio’s Integrate tab, and the code panel next to the marketplace Run form, add the X-Env-Keys line to the cURL snippet for you. Keys you have not mapped yet show up as a <secret_id> placeholder; pick a secret in the Env section and the snippet resolves it to the real id, so what you copy is a request that runs.
Reading an external key in your code
Nothing special. By the time your handler runs, an external key is an ordinary environment variable holding the runner’s value:
import os
def cortexone_handler(event, context): api_key = os.environ.get("OPENAI_API_KEY") if not api_key: return {"statusCode": 400, "body": {"error": "OPENAI_API_KEY was not supplied"}} # use api_key ... return {"statusCode": 200, "body": {"ok": True}}See Secrets for the equivalent in Python 3.13 - Fast, JavaScript, and Lua.
Since an optional key can legitimately be absent, and a required one can be absent on a direct API call, always read with a get-style accessor and handle the missing case rather than indexing straight into the environment.