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

# Molecule API Authentication: Trading Keys and JWTs

> Molecule enforces Ed25519 signing for trading routes and JWT bearer tokens for management routes. Learn which credential to use for each class of endpoint.

Molecule enforces two distinct authentication modes because trading operations and account management carry different trust models. Trading routes require cryptographic proof that the request originated from a process holding a registered private key — no shared secret or session token can substitute. Management routes (account configuration, org-level controls) require a human session JWT issued through the Molecule dashboard. Passing the wrong credential to either class of endpoint produces a hard error, not a silent fallback.

***

## Trading Key Authentication (Ed25519)

Every trading request is authenticated by an Ed25519 signature computed locally in your process. The flow is:

1. You generate an Ed25519 keypair and register the public key with Molecule.
2. For each request, the SDK computes a canonical string from the HTTP method, path, sorted query parameters, timestamp, and SHA-256 body hash.
3. The SDK signs that string with your private key and attaches three headers to the outgoing request.

Your private key never leaves your process. Molecule only stores the public key and uses it to verify the signature on arrival.

### Signing headers

| Header | Content |
| - | - |
| `X-Molecule-Key-Id` | Your registered key identifier |
| `X-Molecule-Timestamp` | Unix milliseconds at time of signing |
| `X-Molecule-Signature` | Base64-encoded Ed25519 signature over the canonical string |

The SDK attaches all three headers automatically whenever you construct a client with `key_id` and `private_key`. You do not need to compute or attach them yourself.

### Initialize the client with a trading key

Pass your `key_id` and `private_key` directly to the `Molecule` constructor. The `base_url` is required — set it via the `MOLECULE_BASE_URL` environment variable or pass it explicitly.

<CodeGroup>
  ```python Synchronous theme={"dark"}
  import os
  from molecule import Molecule

  client = Molecule(
      base_url=os.environ["MOLECULE_BASE_URL"],
      key_id=os.environ["MOLECULE_KEY_ID"],
      private_key=os.environ["MOLECULE_PRIVATE_KEY"],
  )

  # Every request is signed automatically — no extra steps needed
  venues = client.markets.venues()
  ```

  ```python Asynchronous theme={"dark"}
  import os
  from molecule import AsyncMolecule

  client = AsyncMolecule(
      base_url=os.environ["MOLECULE_BASE_URL"],
      key_id=os.environ["MOLECULE_KEY_ID"],
      private_key=os.environ["MOLECULE_PRIVATE_KEY"],
  )

  venues = await client.markets.venues()
  ```

  ```python Environment variables only theme={"dark"}
  # If MOLECULE_BASE_URL, MOLECULE_KEY_ID, and MOLECULE_PRIVATE_KEY are
  # all set, you can omit them from the constructor entirely.
  from molecule import Molecule

  client = Molecule()
  venues = client.markets.venues()
  ```
</CodeGroup>

### Accepted private key formats

The SDK's `load_private_key` function accepts the following representations of a 32-byte Ed25519 seed:

| Format | Example |
| - | - |
| **Base64 (canonical)** | `"cgxIy78BcAUooPsgrAXQPYDW5h0zLru6Lg35Ga4pEP0="` |
| **Hex string** | `"720c48cbbf01700528a0fb20ac05d03d..."` |
| **Raw bytes** | `bytes` object of length 32 |
| **64-byte seed\|\|pubkey blob** | Base64 or bytes of length 64 — truncated to the first 32 bytes |

The canonical format is base64 of the 32-byte seed, matching what the key-generation snippet in [Key Management](/authentication/key-management) produces. Use that format for all stored credentials.

***

## JWT Token Authentication (Management Routes)

Account management routes — such as configuring organizations, managing users, and accessing dashboard-level resources — require a JWT issued by the Molecule platform. Pass the token to the `token=` parameter of the constructor. The SDK sends it as an `Authorization: Bearer <token>` header.

<Warning>
  The two authentication modes are **not interchangeable**. Using a trading key (Ed25519) on a management route returns `403 human_session_required`. Using a JWT token on a trading route returns `401 unauthorized`. There is no automatic fallback.
</Warning>

```python theme={"dark"}
import os
from molecule import Molecule

# Obtain this JWT from the Molecule dashboard or your SSO provider
mgmt_client = Molecule(
    base_url=os.environ["MOLECULE_BASE_URL"],
    token=os.environ["MOLECULE_MGMT_TOKEN"],
)
```

<Note>
  Do not pass both `private_key` and `token` to the same client instance. When both are present, the SDK prioritises Ed25519 signing and ignores the token. Use separate client instances for trading and management operations.
</Note>

***

## Error reference

The table below maps error codes to their Python exception class and the remediation action.

| HTTP | Error code | Exception | Cause and action |
| - | - | - | - |
| `401` | `unauthorized` | `UnauthorizedError` | Request could not be authenticated. Verify your `key_id` and `private_key` are correct and that the key is registered. |
| `401` | `signature` | `UnauthorizedError` | Signature verification failed. Check that the canonical string is being computed correctly and that no proxy is modifying headers. |
| `401` | `replay` | `UnauthorizedError` | The same signature was received twice within the 30-second replay window. Ensure each request uses a fresh timestamp. |
| `401` | `stale_timestamp` | `UnauthorizedError` | The `X-Molecule-Timestamp` is more than 30 seconds outside server time. Synchronise your system clock. |
| `403` | `human_session_required` | `ForbiddenError` | A trading key was used on a management-only route. Use a JWT token for this endpoint. |
| `403` | `forbidden` | `ForbiddenError` | The authenticated principal does not have permission for this resource. |

```python theme={"dark"}
from molecule import Molecule, UnauthorizedError, ForbiddenError
import os

client = Molecule(
    base_url=os.environ["MOLECULE_BASE_URL"],
    key_id=os.environ["MOLECULE_KEY_ID"],
    private_key=os.environ["MOLECULE_PRIVATE_KEY"],
)

try:
    result = client.orders.list(subaccount_id="sa_01")
except UnauthorizedError as exc:
    # exc.error: "unauthorized" | "signature" | "replay" | "stale_timestamp"
    print(f"Auth failed [{exc.error}]: {exc.message}")
except ForbiddenError as exc:
    # exc.error: "forbidden" | "human_session_required" | "kill_switch"
    print(f"Access denied [{exc.error}]: {exc.message}")
```

***

## Next steps

<CardGroup cols={2}>
  <Card title="Key Management" icon="key" href="/authentication/key-management">
    Generate an Ed25519 keypair, export the seed in the canonical base64 format, and load it securely in your process.
  </Card>

  <Card title="Request Signing" icon="pen-nib" href="/security/request-signing">
    Understand the canonical string construction, replay protection, and WebSocket signing in detail.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.