Manage secrets in a flow
Store an API key on the console's Secrets page, read it from node code with ax.secrets.get, and know exactly what is and is not encrypted.
View as MarkdownTo give a flow a credential — an LLM API key, a database connection string —
register it once as a secret in the console, then read it from node code with
ax.secrets.get("NAME"). Secret values are encrypted at rest, never shown
again after you save them, and decrypted by the platform only when an
execution starts. Node code never sees other tenants' secrets, and secrets
never appear in package source or flow definitions.
Prerequisites
- You are logged in to the Axiom app (Installation).
- To read a secret from code, you need a package with at least one node — see Your first node or the per-language guides such as Create a node in Python.
Add a secret in the console
- In the app, go to Console → Secrets (
/console/secrets). - Click Add secret.
- Enter a Name (for example
ANTHROPIC_API_KEY) and a Value. The value field is a password-style input with a show/hide toggle. - Click Save secret.

What to expect after saving:
- The value is never shown again. The secrets list displays only the name and the date added; no console page or API response ever returns a stored value.
- Saving an existing name replaces the value. The form warns you and requires an explicit I understand, replace it confirmation before it lets you overwrite.
- Deletion is immediate. The delete (trash) button on a secret's row removes it; the next execution will no longer see it.
- Secrets are tenant-scoped, not per flow. Every secret registered here is readable by every node in every flow of your tenant. To give one flow config a different value, use a config-scoped override (see "Override a secret for one flow config" below).
Read a secret from node code
Inside a node handler, call the secrets accessor on AxiomContext. It
returns the plaintext value plus a found flag — a missing secret yields an
empty value and false, never an exception:
# nodes/greet.py — a node whose package defines GreetRequest/GreetReply
from gen.messages_pb2 import GreetRequest, GreetReply
from gen.axiom_context import AxiomContext
def greet(ax: AxiomContext, input: GreetRequest) -> GreetReply:
"""Greets the caller, signing with the tenant's configured signature."""
signature, ok = ax.secrets.get("GREETER_SIGNATURE")
if not ok:
signature = "anonymous"
return GreetReply(greeting=f"Hello {input.name}, from {signature}")The same accessor exists in every SDK language:
| Language | Call form | Returns |
|---|---|---|
| Python | ax.secrets.get("NAME") | (value, ok) tuple |
| Go | ax.Secrets().Get("NAME") | (string, bool) |
| TypeScript | ax.secrets.get("NAME") | [value, ok] tuple |
| Rust | ax.secrets().get("NAME") | (String, bool) |
| Java | ax.secrets().get("NAME") | Optional<String> |
| C# | ax.Secrets().Get("NAME") | (string Value, bool Found) |
All of the tenant's registered secrets are resolved when an execution
starts, so a node can read any secret by name without per-flow wiring. In
unit tests, the generated test file for each node ships a mock
AxiomContext you can load with test values — for example, the Python
mock accepts secrets_map={"GREETER_SIGNATURE": "test-sig"}.
Declare required secrets in axiom.yaml
A node that reads secrets should declare their names in its axiom.yaml
entry so users know what to register before invoking a flow that contains
it:
# axiom.yaml
name: my-org/greeter
version: 0.1.0
language: python
nodes:
- name: Greet
input: GreetRequest
output: GreetReply
required_secrets:
- GREETER_SIGNATUREThe marketplace displays required_secrets on the package listing.
axiom validate scans node source for secret reads and warns — it does not
fail — when a name referenced in code is missing from the list. The
declaration is informational: the platform does not block invocation when a
declared secret is unregistered; the node simply receives a not-found result
at read time.
What is and is not encrypted
Encrypted at rest:
- Secret values saved on the Secrets page.
- Config-scoped secret overrides saved on a flow config.
Stored and displayed in plain form:
- Secret names and timestamps — they appear in the console list, so do not put sensitive data in a secret's name.
- Flow config parameters — config parameter values are not encrypted. Put credentials in a secret (or a config's secret override), never in an ordinary config parameter. See Flow configs.
At runtime, the platform decrypts values when an execution starts and
delivers them to node code only through the sidecar. The platform never
returns a value through the management API and never writes one to its own
logs or traces. After ax.secrets.get hands your code the plaintext,
hygiene is your code's responsibility: do not log a secret with ax.log,
copy it into an output message, or write it to agent memory — those surfaces
are not encrypted secret storage, and data placed there is visible in
execution and debug views. See
Sandboxing and tenancy for the
isolation model.
Override a secret for one flow config
A flow config can carry its own value for a secret name, replacing the tenant-wide value for executions invoked with that config:
- Go to Console → Flow Configs (
/console/flow-configs). - In a config's form, open Secret overrides — pick the secret name and enter the override value (same masked input as the Secrets page).
- Save the config. The list shows which names a config overrides — never the values.
Overrides are encrypted at rest like ordinary secrets and are scoped to the
one config that owns them. An execution invoked with that config reads the
override through the same ax.secrets.get call; node code cannot tell the
difference. See Flow configs for selecting a config at
invocation time.