Public beta — not for production use. Data may be wiped at any time. Questions? Contact us.
Documentation menu

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 Markdown

To 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

Add a secret in the console

  1. In the app, go to Console → Secrets (/console/secrets).
  2. Click Add secret.
  3. Enter a Name (for example ANTHROPIC_API_KEY) and a Value. The value field is a password-style input with a show/hide toggle.
  4. Click Save secret.

The Secrets page in the console with one saved secret named ANTHROPIC_API_KEY in the list, showing only its name and the date it was added, with a delete button on the row

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:

LanguageCall formReturns
Pythonax.secrets.get("NAME")(value, ok) tuple
Goax.Secrets().Get("NAME")(string, bool)
TypeScriptax.secrets.get("NAME")[value, ok] tuple
Rustax.secrets().get("NAME")(String, bool)
Javaax.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_SIGNATURE

The 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:

  1. Go to Console → Flow Configs (/console/flow-configs).
  2. In a config's form, open Secret overrides — pick the secret name and enter the override value (same masked input as the Secrets page).
  3. 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.