---
title: "Manage secrets in a flow"
description: "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."
category: guide
surfaces: [console, sdk, cli]
related: [concepts/sandboxing-and-tenancy, guides/flow-configs, guides/api-keys, reference/axiom-yaml, reference/sdk/python, reference/sdk/go, reference/sdk/typescript]
last_reviewed: 2026-06-10
---

# Manage secrets in a flow

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

- You are logged in to the Axiom app
  ([Installation](../getting-started/installation.md)).
- To read a secret from code, you need a package with at least one node —
  see [Your first node](../getting-started/first-node.md) or the
  per-language guides such as
  [Create a node in Python](./create-a-node-python.md).

## 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](../assets/screenshots/console-secrets-page.png)

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:

```python
# 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:

```yaml
# 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](./flow-configs.md).

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](../concepts/sandboxing-and-tenancy.md) 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](./flow-configs.md) for selecting a config at
invocation time.
