> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nelieo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# NSP API Key Management: Generate, Rotate, and Revoke

> How to generate NSP API keys with keygen, authenticate every request using the X-Substrate-Key header, configure axon.toml, and safely rotate credentials.

NSP protects its REST and WebSocket API with bearer-style API keys. Every request to the daemon — whether from your own agent code, a curl command, or the Python SDK — must include a valid key in the `X-Substrate-Key` header. The daemon verifies the key by comparing a BLAKE3 hash; the plaintext value is never stored on disk.

<Warning>
  Never commit API keys to source control. Store them in environment variables or a secrets manager. The Python SDK reads `NSP_API_KEY` automatically so you can avoid putting the key in code at all.
</Warning>

## Generating a Key

Run `keygen` from the directory where `axon-daemon.exe` lives, or from any terminal where it is on your `PATH`:

```bash theme={null}
axon-daemon.exe keygen
```

The daemon writes a new entry to `axon_keys.json` and prints the plaintext key once — this is the only time you will see it:

```text theme={null}
✅ Generated new API key: sk_nsp_a1b2c3d4e5f6789...
   Key ID: key_01j5abc123
   Added to: C:\ProgramData\Nelieo\axon_keys.json
   Label: (unlabeled)
```

Add a `--label` to track which key belongs to which agent:

```bash theme={null}
axon-daemon.exe keygen --label "prod-agent-gmail"
```

<Tip>
  Use one key per agent process. Separate labels make it easy to disable a single agent's access without affecting others.
</Tip>

## Key Format

All NSP API keys start with the prefix `sk_nsp_` followed by a random alphanumeric string:

```text theme={null}
sk_nsp_a1b2c3d4e5f6789abcdef0123456789abcdef
```

Keys are case-sensitive. Treat them like passwords.

## Enabling Authentication in axon.toml

Open `axon.toml` (located in the daemon's working directory or `C:\ProgramData\Nelieo\`) and confirm the `[auth]` section is configured:

```toml theme={null}
[auth]
require_key = true
keys_file   = "C:\\ProgramData\\Nelieo\\axon_keys.json"
```

The daemon reloads `axon_keys.json` every 5 seconds without requiring a restart, so you can add, disable, or delete keys without any downtime.

<Note>
  If `require_key` is `false`, the daemon prints a warning every 60 seconds. Treat that warning as a deployment blocker — disable auth only in isolated local development environments.
</Note>

## The Keys File

All keys live in the JSON file pointed to by `keys_file`. The daemon stores only the BLAKE3 hash — you cannot recover the original key from this file.

```json theme={null}
{
  "keys": [
    {
      "id": "key_01j5abc123",
      "key_hash": "blake3:a3f8c9d2e1b6f5...",
      "label": "prod-agent-gmail",
      "created_at": "2026-07-20T13:00:00Z",
      "last_used_at": "2026-07-20T18:30:00Z",
      "enabled": true
    },
    {
      "id": "key_01j5xyz789",
      "key_hash": "blake3:b4g9d0e2c7...",
      "label": "dev-local",
      "created_at": "2026-07-01T09:00:00Z",
      "last_used_at": null,
      "enabled": true
    }
  ]
}
```

To revoke a key while preserving the audit trail, set `"enabled": false` rather than deleting the entry:

```json theme={null}
{
  "id": "key_01j5abc123",
  "key_hash": "blake3:...",
  "enabled": false
}
```

## Authenticating Requests

Pass the key in the `X-Substrate-Key` header on every request.

<Tabs>
  <Tab title="curl">
    **With a valid key:**

    ```bash theme={null}
    curl -H "X-Substrate-Key: sk_nsp_a1b2c3d4e5f6789..." \
         http://localhost:7842/substrate/v1/apps
    ```

    **Without a key (returns 401):**

    ```bash theme={null}
    curl http://localhost:7842/substrate/v1/apps
    ```

    ```json theme={null}
    {
      "error": {
        "code": "missing_key",
        "message": "This endpoint requires authentication. Add the X-Substrate-Key header."
      }
    }
    ```
  </Tab>

  <Tab title="Python SDK">
    The Python SDK accepts the key directly or reads the `NSP_API_KEY` environment variable:

    ```python theme={null}
    import nelieo_nsp as nsp

    # Option 1: environment variable (recommended for production)
    # export NSP_API_KEY=sk_nsp_a1b2c3d4...
    async with nsp.NSPClient() as client:
        apps = await client.list_apps()

    # Option 2: pass the key explicitly (acceptable for scripts)
    async with nsp.NSPClient(api_key="sk_nsp_a1b2c3d4...") as client:
        apps = await client.list_apps()

    # Option 3: retrieve from a secrets manager (recommended for production)
    import boto3
    secrets   = boto3.client("secretsmanager")
    api_key   = secrets.get_secret_value(SecretId="prod/nsp-api-key")["SecretString"]
    async with nsp.NSPClient(api_key=api_key) as client:
        apps = await client.list_apps()
    ```
  </Tab>
</Tabs>

## The NSP\_API\_KEY Environment Variable

The Python SDK automatically reads `NSP_API_KEY` from the process environment when you do not pass `api_key` to `NSPClient`. Set this variable in your shell profile, a `.env` file (excluded from source control), or your deployment environment:

```bash theme={null}
# Linux / macOS
export NSP_API_KEY=sk_nsp_a1b2c3d4...

# Windows (PowerShell)
$env:NSP_API_KEY = "sk_nsp_a1b2c3d4..."
```

## 401 Error Responses

The daemon returns `401 Unauthorized` for three distinct authentication failures:

| HTTP Status | Error Code     | Cause                                             |
| ----------- | -------------- | ------------------------------------------------- |
| `401`       | `missing_key`  | No `X-Substrate-Key` header in the request        |
| `401`       | `invalid_key`  | Header present but key not found in the key store |
| `401`       | `disabled_key` | Key found but `"enabled": false` in the keys file |

All three return the same JSON envelope:

```json theme={null}
{
  "error": {
    "code": "invalid_key",
    "message": "The provided X-Substrate-Key is not recognized. Generate a new key with: axon-daemon.exe keygen"
  }
}
```

In the Python SDK, all three map to `NSPAuthError`. Inspect `e.code` to distinguish them:

```python theme={null}
try:
    apps = await client.list_apps()
except nsp.NSPAuthError as e:
    if e.code == "missing_key":
        print("No key was sent — check your NSPClient constructor.")
    elif e.code == "invalid_key":
        print("Key not recognised — run: axon-daemon.exe keygen")
    elif e.code == "disabled_key":
        print("Key is disabled — set enabled: true in axon_keys.json")
```

## Rotating Keys

Follow these steps when rotating a key — for example, during a periodic security rotation or after a suspected leak:

<Steps>
  <Step title="Generate a replacement key">
    ```bash theme={null}
    axon-daemon.exe keygen --label "prod-agent-v2"
    ```

    Copy the printed `sk_nsp_...` value and store it in your secrets manager.
  </Step>

  <Step title="Update your agent configuration">
    Update `NSP_API_KEY` in your deployment environment or secrets manager to point to the new key.
  </Step>

  <Step title="Verify the new key works">
    ```bash theme={null}
    curl -H "X-Substrate-Key: sk_nsp_<new-key>" \
         http://localhost:7842/substrate/v1/health
    ```

    Confirm you receive a `200 OK` response before proceeding.
  </Step>

  <Step title="Disable the old key">
    Open `axon_keys.json` and set `"enabled": false` on the old entry. The daemon picks up the change within 5 seconds.
  </Step>

  <Step title="Delete the old key after a grace period">
    After 24 hours with no authentication errors using the new key, delete the old key entry from `axon_keys.json` to keep the file tidy.
  </Step>
</Steps>
